React+TS前台项目实战(十二)-- 全局常用组件Toast封装,以及rxjs和useReducer的使用

文章目录

  • 前言
  • Toast组件
    • 1. 功能分析
    • 2. 代码+详细注释
      • (1)建立一个reducer.ts文件,用于管理状态数据
      • (2)自定义一个清除定时器的hook
      • (3)使用rxjs封装全局变量管理hook
      • (4)在toast组件中引入上述封装文件
    • 3. 使用方式
    • 4. toast动画效果展示
  • 总结


前言

今天这篇讲的这个组件,是一个用于全局提示的 React灵巧组件。


Toast组件

1. 功能分析

(1)使用 state.toasts 数组和 ToastItem 组件来渲染 toast 消息列表
(2)ToastItem 组件用于渲染单个 toast 消息,并使用渐隐动画
(3)useSetToast 函数返回一个回调函数,用于将 toast 消息设置到全局状态中
(4)组件从全局状态中获取当前的 toast 消息,并使用 useToastData hook 获取状态管理函数
(5)useEffect hook 用于在接收到 toast 消息时将其添加到状态中
(6)当从状态中移除 toast 消息时,会调用 willLeave 函数来更新状态并触发渐隐动画

2. 代码+详细注释

(1)建立一个reducer.ts文件,用于管理状态数据

import { useReducer } from 'react'
export interface ToastMessage {message: stringtype: 'success' | 'warning' | 'danger'duration?: numberid: number
}
interface State {toasts: ToastMessage[]toast: string
}
interface Action {type: 'ADD' | 'REMOVE'payload: {toast: ToastMessage}
}
// 初始状态
const initialState: State = {toasts: [],toast: '',
}
const reducer = (state: State, action: Action) => {switch (action.type) {case 'ADD':return {...state,toasts: state.toasts.concat(action.payload.toast),}case 'REMOVE':return {...state,toasts: state.toasts.filter((toast: ToastMessage) => toast.id !== action.payload.toast.id),}default:return state}
}export const useToastData = () => {const [state, dispatch] = useReducer(reducer, initialState)return {state, dispatch}
}

(2)自定义一个清除定时器的hook

// 定义一个自定义的Hook,用于在组件卸载时清除定时器
// 参数:
// - callback:定时器触发时执行的回调函数
// - clearCallback:定时器卸载时执行的清除回调函数
// - delay:定时器延迟执行的时间
export const useTimeoutWithUnmount = (callback: () => void, clearCallback: () => void, delay: number) => {// 使用useRef保存回调函数和清除回调函数的引用const savedCallback = useRef(() => {})const savedClearCallback = useRef(() => {})// 在组件挂载时注册回调函数和清除回调函数useEffect(() => {savedCallback.current = callbacksavedClearCallback.current = clearCallback})// 在组件挂载和卸载时设置定时器useEffect(() => {// 定义定时器的回调函数const tick = () => {// 执行保存的回调函数savedCallback.current()}// 设置定时器,执行tick函数,并返回定时器的IDconst listener = setTimeout(tick, delay)// 返回一个清除定时器的函数return () => {// 清除定时器clearTimeout(listener)// 执行清除回调函数savedClearCallback.current()}}, [delay])
}

(3)使用rxjs封装全局变量管理hook

import { useObservableState } from 'observable-hooks'
import { Dispatch, SetStateAction, useCallback } from 'react'
import { BehaviorSubject } from 'rxjs'// 全局状态的类型定义
export type GlobalState<T> = BehaviorSubject<T>// 创建一个全局状态
export function createGlobalState<T>(initState: T): GlobalState<T> {return new BehaviorSubject<T>(initState)
}// 设置全局状态的值
export function setGlobalState<T>(globalState: GlobalState<T>, value: T) {globalState.next(value)
}// 获取全局状态的值
export function getGlobalState<T>(globalState: GlobalState<T>): T {return globalState.getValue()
}// 创建一个全局状态的设置函数
export function createGlobalStateSetter<T>(globalState: GlobalState<T>): (value: T) => void {return (value: T) => setGlobalState(globalState, value)
}// 使用全局状态的 hook
export function useGlobalState<T>(globalState: GlobalState<T>): [T, Dispatch<SetStateAction<T>>] {// 通过 useObservableState 获取全局状态的值const state = useObservableState(globalState)// 设置全局状态的值的函数const setState = useCallback<Dispatch<SetStateAction<T>>>((state: T | ((prevState: T) => T)) => {// TODO: 这里使用了 `as` 关键字,因为 `T` 没有约束来禁止状态的函数类型。// 但是实现这种约束会很困难,所以暂时使用 `as` 解决。const finalState =typeof state === 'function' ? (state as (prevState: T) => T)(getGlobalState(globalState)) : stateglobalState.next(finalState)},[globalState],)return [state, setState]
}

(4)在toast组件中引入上述封装文件

// @/components/Toast/index.tsx
import { useState, useEffect, useCallback } from 'react'
import { useTimeoutWithUnmount } from '@/hooks'
import { ToastItemPanel, ToastPanel } from './styled'
import { createGlobalState, useGlobalState } from '@/utils/state'
import { useToastData, ToastMessage } from './reducer'/*** 根据不同的toast类型返回对应的颜色* @param {ToastMessage['type']} type - 类型,可选值为'success'、'warning'、'danger'* @returns {string} - 对应的颜色值*/
const getColor = (type: ToastMessage['type']) => {switch (type) {case 'success':return 'var(--primary-color)'case 'warning':return '#ffae42'case 'danger':return '#D03A3A'default:return '#3cc68a'}
}const ANIMATION_DISAPPEAR_TIME = 2000
const MAX_FRAME: number = (ANIMATION_DISAPPEAR_TIME / 1000) * 40 // suppose fps = 40
const DEFAULT_TOAST_DURATION = 3000// ToastItem 组件用于渲染一个 toast 消息
const ToastItem = ({ data, willLeave }: { data: ToastMessage; willLeave: Function }) => {// 初始化透明度为1const [opacity, setOpacity] = useState(1)let animationId: number = 0// 定义一个定时器,在指定时间后执行 willLeave 函数,实现 toast 消息的逐渐消失效果useTimeoutWithUnmount(() => {const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFramelet count: number = 0// 定义一个更新透明度的函数,每次调用都会递增 count,并根据 count 的值计算透明度const updateOpacity = () => {count++setOpacity(1 - count / MAX_FRAME)if (count < MAX_FRAME) {requestAnimationFrame(updateOpacity)} else {// 如果执行完一轮动画后,清除定时器willLeave()}}animationId = requestAnimationFrame(updateOpacity)},() => {if (animationId) {const cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFramecancelAnimationFrame(animationId)}},data.duration || DEFAULT_TOAST_DURATION,)// 渲染 toast 消息return (<ToastItemPanelstyle={{opacity,background: getColor(data.type),}}><div className="toastText">{data.message}</div></ToastItemPanel>)
}// 创建全局状态,用于存储 toast 消息
const globalToast = createGlobalState<ToastMessage | null>(null)// 返回一个函数,用于设置 toast 消息
export function useSetToast() {const [, setToast] = useGlobalState(globalToast)return useCallback((data: Pick<ToastMessage, 'message' | 'duration'> & Partial<Pick<ToastMessage, 'type'>>) =>setToast({id: new Date().getTime(),message: data.message,type: data.type ?? 'success',duration: data.duration,}),[setToast],)
}// Toast 组件是一个提供 toast 消息展示的组件
export default () => {// 获取全局状态中的 toast 消息const [toast] = useGlobalState(globalToast)// 获取 toast 消息的状态和 dispatch 函数const { state, dispatch } = useToastData()useEffect(() => {// 如果 toast 消息不为空,则将其添加到状态中if (toast) {dispatch({type: 'ADD',payload: {toast,},})}}, [dispatch, toast])// 如果状态中没有 toast 消息,则返回 null,否则渲染 toast 消息列表return state.toasts.length === 0 ? null : (<ToastPanel className="toast">{state.toasts &&state.toasts.map((item: ToastMessage) => (// 渲染每个 toast 消息,并在消失后通过 dispatch 函数将其从状态中移除<ToastItemwillLeave={() => {dispatch({type: 'REMOVE',payload: {toast: item,},})}}key={item.id}data={item}/>))}</ToastPanel>)
}
------------------------------------------------------------------------------
// @/components/Toast/styled.tsx
import styled from 'styled-components'
import variables from '@/styles/variables.module.scss'
export const ToastPanel = styled.div`position: absolute;position: -webkit-absolute;top: 0;width: 100%;height: 100%;box-sizing: border-box;display: flex;z-index: 9998;flex-direction: column;pointer-events: none;
`
export const ToastItemPanel = styled.div`width: 100%;position: fixed;position: -webkit-fixed;top: var(--navbar-height);opacity: 0.96;z-index: 9999;height: 60px;.toastText {color: white;font-size: 20px;line-height: 60px;text-align: center;}@media (max-width: ${variables.mobileBreakPoint}) {top: 42px;height: 36px;.toastText {font-size: 14px;line-height: 36px;}}@media (max-width: 320px) {top: 42px;height: 36px;.toastText {font-size: 12px;line-height: 36px;}}
`

3. 使用方式

// 在layout布局文件中使用Toast组件
import Toast from '@/components/Toast'
// 添加到layout布局文件中,具体layout文件代码看:https://blog.csdn.net/weixin_43883615/article/details/139505250
<Page><Header /><Suspense fallback={<span>loading...</span>}><ErrorBoundary><Content><Outlet /></Content></ErrorBoundary></Suspense><Footer /><Toast />
</Page>
------------------------------------------------------------------------------------
// 在需要使用的组件中引入
import { useSetToast } from '@/components/Toast'
// 定义与使用
const setToast = useSetToast()
// 成功效果
setToast({ message: '哦豁弹窗成功了', type: 'success' })
// 警告效果
setToast({ message: '哦豁弹窗成功了', type: 'danger' })

4. toast动画效果展示

在这里插入图片描述
在这里插入图片描述


总结

下一篇讲【全局常用组件Header封装】。关注本栏目,将实时更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/32026.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文字模拟:经营酒店隐私政策

隐私政策 文字模拟&#xff1a;经营酒店隐私政策 一、引言 本隐私政策适用于我们提供的文字模拟&#xff1a;经营酒店小游戏&#xff08;以下简称“游戏”&#xff09;。我们非常重视用户的隐私和个人信息的保护&#xff0c;因此制定了本隐私政策&#xff0c;以解释我们如何…

在scrapy中使用Selector提取数据

经院吉吉&#xff1a; 首先说明一下&#xff0c;在scrapy中使用选择器是基于Selector这个对象滴&#xff0c;selector对象在scrapy中通过XPATH或是CSS来提取数据的&#xff0c;我们可以自己创建selector对象&#xff0c;但在实际开发中我们不需要这样做&#xff0c;因为respons…

御道源码(ruoyi-vue-pro)个人使用小结

御道源码&#xff08;ruoyi-vue-pro&#xff09;个人使用小结 一、Git地址 1、平台项目简介及地址 2、开发指南&#xff0c;如图所示&#xff0c;部分功能需要收费&#xff0c;可自行了解 二、项目文件夹结构示例&#xff1a; 三、技术介绍 1.基于 Spring Boot MyBatis P…

Java字符串连接符拼接操作

在Java的算术运算符中的加法符号“ ”&#xff0c;可以用来进行算术运算&#xff0c;也可以用来当作连接符进行字符串的拼接。 当“ ”操作中出现字符串时&#xff0c;这个“ ”是字符串连接符&#xff0c;而不是运算符了。 会将前后的数据进行拼接在一起&#xff0c;并产生…

dll丢失应该怎么解决,总结5种解决DLL丢失问题的方法

在数字时代&#xff0c;我们与计算机的每一天都密不可分。然而&#xff0c;就像所有技术产品一样&#xff0c;我们的计算设备也时不时地会出现一些问题&#xff0c;让人头疼不已。就在上周&#xff0c;我遭遇了一个令人崩溃的技术挑战——DLL文件丢失。这个看似微不足道的小问题…

转--基于OpenEuler的Docker容器安装使用

/usr/sbin/sshd执行以下命令查看ssh服务是否已经开始监听22端口&#xff1a; netstat -tuln | grep :22看到以下输出证明ssh服务已启动&#xff1a; [rootmaster /]# netstat -tuln | grep :22 tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN …

【MySQL】 -- 事务

如果对表中的数据进行CRUD操作时&#xff0c;不加控制&#xff0c;会带来一些问题。 比如下面这种场景&#xff1a; 有一个tickets表&#xff0c;这个数据库被两个客户端机器A和B用时连接对此表进行操作。客户端A检查tickets表中还有一张票的时候&#xff0c;将票出售了&#x…

OpenCloudOS系统上安装Java环境

在腾讯云OpenCloudOS系统上安装Java环境&#xff0c;可以使用yum包管理器进行安装。以下是安装Java环境的步骤和示例代码&#xff1a; 首先打开终端。 执行以下命令以更新yum包索引&#xff1a; sudo yum update 安装OpenJDK Java环境&#xff0c;可以选择安装Java 8或者更…

【Linux基础IO】深入理解缓冲区

缓冲区在文件操作的过程中是比较重要的&#xff0c;理解缓冲区向文件刷新内容的原理可以更好的帮助我们更深层的理解操作系统内核对文件的操作。 FILE 因为IO相关函数与系统调用接口对应&#xff0c;并且库函数封装系统调用&#xff0c;所以本质上&#xff0c;访问文件都是通过…

ES数值类型慢查询优化

现象 某个查询ES接口慢调用告警&#xff0c;如图&#xff0c;接口P999的耗时都在2500ms: 基本耗时都在查询ES阶段&#xff1a; 场景与ES设定 慢调用接口为输入多个条件分页查询&#xff0c;慢调用接口调用的ES索引为 express_order_info&#xff0c;该索引通过DTS(数据同步…

STM32人工智能检测-筛选机器人

前言 本文描述了一种使用STM32进行机器人筛选的办法。筛选对象是我的粉s&#xff0c;删选办法是瞪眼法。 问题现象 每次当我的STM32 向外界发出一篇新的的报文&#xff0c;总能在1H之内得到focus&#xff0c;格式如下 [title][body][tail]于是我对各个focus 我报文的对象进…

Redis数据过期、淘汰策略

数据过期策略&#xff1a; 惰性删除&#xff1a; 设置该key过期时间后&#xff0c;我们不去管它&#xff0c;当需要该key时&#xff0c;我们在检查其是否过期&#xff0c;如果过期&#xff0c;我们就删掉它&#xff0c;反之返回该key。 这种方式对cpu友好&#xff08;只在用…

浏览器/H5复制链接功能

方法1&#xff1a;execCommand copyLink(){//复制链接--execCommandlet input document.createElement(input); //创建一个input标签input.value this.shareForm.url; //复制的内容&#xff0c;没有先获取标签document.body.appendChild(input);//将input添加的document中in…

C# OCCT Winform 界面搭建

目录 1.创建一个WInform项目 2.代码总览 代码解析 3.添加模型到场景 4.鼠标交互 1.创建一个WInform项目 2.代码总览 using Macad.Occt.Helper; using Macad.Occt; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Remoting.Co…

PHP学习总结-入门篇

PHP简介 PHP (Hypertext Preprocessor)&#xff0c;即“超文本预处理器”。PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。PHP语法吸收了C语言、Java和Perl的特点&#xff0c;便于学习。PHP 是开源免费的&#xff0c;主要适用于Web开发领域&#xff0c;使用广泛。…

vue简介实例

先看样式 再看代码 <div v-else class"relative mt-4 h-44 cursor-pointer overflow-hidden rounded-xl"><divclass"absolute flex h-44 w-full blur-lg":style"{ backgroundImage: url(${currentSongList.list[0]?.coverImgUrl}) }"…

1394. 找出数组中的幸运数

在整数数组中&#xff0c;如果一个整数的出现频次和它的数值大小相等&#xff0c;我们就称这个整数为「幸运数」。 给你一个整数数组 arr&#xff0c;请你从中找出并返回一个幸运数。 如果数组中存在多个幸运数&#xff0c;只需返回 最大 的那个。如果数组中不含幸运数&#…

STM32单片机USART串口收发数据包

文章目录 1. 串口通信 1.1 串口初始化 1.2 库函数 2. 串口收发HEX数据包 2.1 Serial.c 2.2 Serial.h 2.3 main.c 3. 串口收发文本数据包 3.1 Serial.c 3.2 Serial.h 3.3 main.c​​​​​​​ 1. 串口通信 对于串口通信的详细​​​​​​​解析可以看下面这篇文章…

Java 图书管理系统功能实现

承接上一篇的 图书管理系统 &#xff0c;点击这里跳转 要实现什么功能 1.查找图书 2.增加图书 3.删除图书 4.展示图书 5.退出系统 6.借阅图书 7.归还图书 1.查找图书 要完成这个功能需要以下步骤 输入书名&#xff0c; 然后在书架里找到这本书打印出来&#xff0c;…

05-5.3.2_2 二叉树的线索化

&#x1f44b; Hi, I’m Beast Cheng &#x1f440; I’m interested in photography, hiking, landscape… &#x1f331; I’m currently learning python, javascript, kotlin… &#x1f4eb; How to reach me --> 458290771qq.com 喜欢《数据结构》部分笔记的小伙伴可以…