当前位置: 首页 > news >正文

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封装】。关注本栏目,将实时更新。

相关文章:

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

文章目录 前言Toast组件1. 功能分析2. 代码详细注释&#xff08;1&#xff09;建立一个reducer.ts文件&#xff0c;用于管理状态数据&#xff08;2&#xff09;自定义一个清除定时器的hook&#xff08;3&#xff09;使用rxjs封装全局变量管理hook&#xff08;4&#xff09;在to…...

總結光學(完)

參考: 陈曦<<光学讲义>>http://ithatron.phys.tsinghua.edu.cn/downloads/optics.pdf 1 波动光学 最简单的一种波是平面波。........... 一个波的波前是指相位相同的点构成的面。波的传播方向垂直于波面。 我们在此将讨论的光波特指波长远大于原子尺度又远小于…...

线程C++

#include <thread> #include <chrono> #include <cmath> #include <mutex> #include <iostream> using namespace std;mutex mtx; void threadCommunicat() {int ans 0;while (ans<3){mtx.lock();//上锁cout << "ans" <…...

DAMA学习笔记(二)-数据治理

1.引言 数据治理&#xff08;Data Governance&#xff0c;DG&#xff09;的定义是在管理数据资产过程中行使权力和管控&#xff0c;包括计划、监控和实施。在所有组织中&#xff0c;无论是否有正式的数据治理职能&#xff0c;都需要对数据进行决策。建立了正式的数据治理规程及…...

07-appium常用操作

一、press_keycode 1&#xff09;方法说明 press_keycode方法是appium的键盘相关函数&#xff0c;可以实现键盘的相关操作&#xff0c;比如返回、按键、音量调节等等。也可以使用keyevent方法&#xff0c;功能与press_keycode方法类似。 常见按键编码&#xff1a;https://www.…...

使用lua开发apisix自定义插件并发布

接到老大需求&#xff1a;需要对cookie进行操作&#xff0c;遂查询apisix的自带插件&#xff0c;发现有&#xff0c;但不满足&#xff0c;于是自己开发了一个插件并部署&#xff0c;把开发部署流程写在这里打个日志怕以后忘掉。 一、需求 插件很简单&#xff0c;就是在reques…...

43 mysql insert select 的实现

前言 我们这里 来探讨一下 insert into $fields select $fields from $table; 的相关实现, 然后 大致来看一下 为什么 他能这么快 按照 我的思考, 应该里里面有 批量插入才对, 但是 调试结果 发现令我有一些意外 呵呵 果然 只有调试才是唯一的真理 测试数据表如下 CREATE…...

趣味学Python,快速上手神奇的itertools库!

大家好&#xff0c;我是菜哥&#xff01; 在学习Python编程的过程中&#xff0c;我们经常会使用到一些非常有用的标准库&#xff0c;它们不仅可以让我们的代码更加简洁高效&#xff0c;还能帮我们解决很多复杂的问题。Python标准库为我们提供了大量实用的工具和模块&#xff0c…...

富文本编辑器CKEditor

介绍 富文本编辑器不同于文本编辑器,它提供类似于 Microsoft Word 的编辑功能 在Django中,有可以现成的富文本三方模块django-ckeditor,具体安排方式: pip install django-ckeditor==6.5.1官网:Django CKEditor — Django CKEditor 6.7.0 documentation 使用方式 创建项…...

【机器学习】音乐大模型的深入探讨——当机器有了创意,是机遇还是灾难?

&#x1f440;国内外音乐大模型基本情况&#x1f440; ♥概述♥ ✈✈✈如FreeCompose、一术科技等&#xff0c;这些企业专注于开发人工智能驱动的语音、音效和音乐生成工具&#xff0c;致力于利用核心技术驱动文化产业升级。虽然具体公司未明确提及&#xff0c;但可以预见的是…...

机器人学习和研究的物质基础包含哪些内容?

为啥写这个&#xff1f; 在很多博客里面提及物质基础&#xff0c;没想到询问的也非常多&#xff0c;写一篇详细一点的。 之前的故事 不合格且失败机器人讲师个人理解的自身课程成本情况-CSDN博客 迷失自我无缘多彩世界-2024--CSDN博客 物质基础与情绪稳定的关系-CSDN博客 …...

Python中的交互式GUI开发:与MATLAB uicontrol的比较

Python中的交互式GUI开发 Python中的交互式GUI开发&#xff1a;与MATLAB uicontrol的比较**Python GUI开发库****Tkinter****PyQt/PySide** **与MATLAB的比较****总结** Python中的交互式GUI开发&#xff1a;与MATLAB uicontrol的比较 在MATLAB中&#xff0c;uicontrol 是一个…...

js 实现将后端请求来的 Blob 数据保存到用户选择的任意目录

js实现将后端请求来的 Blob 数据保存到用户选择的任意目录 实现方式 实现方式 实现方式是使用 window 的 showSaveFilePicker 方法。Window 接口的 showSaveFilePicker() 方法用于显示一个文件选择器&#xff0c;以允许用户保存一个文件。可以选择一个已有文件覆盖保存&#xf…...

【LLM之RAG】RAT论文阅读笔记

研究背景 近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在各种自然语言推理任务上取得了显著进展&#xff0c;尤其是在结合大规模模型和复杂提示策略&#xff08;如链式思维提示&#xff08;CoT&#xff09;&#xff09;时。然而&#xff0c;LLMs 在推理的事实…...

windows anaconda 安装 Labelme

安装 # 创建环境 conda create -n labelme python3.6 #激活环境 conda activate labelme # 安装依赖 conda install pyqt conda install pillow # 安装labelme conda install labelme3.16.2 # 启动labelme labelme右键选择标注类型&#xff0c;从上到下为多边形&#xff08;常…...

Python实现基于深度学习的电影推荐系统

Python实现基于深度学习的电影推荐系统 项目背景 在数字化娱乐时代&#xff0c;用户面临着海量的电影选择。为了帮助用户找到符合个人口味的佳片&#xff0c;MovieRecommendation项目提供了一个基于深度学习的个性化电影推荐系统。该系统利用深度学习技术&#xff0c;根据用户…...

C++ (week9):Git

文章目录 1.git介绍2.git安装3.git配置4.获取自己的SSH公钥5.新建仓库6.邀请开发者7.克隆远程仓库到本地8.在本地进行开发9.本地项目推送到远程仓库10.git的工作原理11.分支管理(1)合作开发的方式(2)分支管理(3)分支合并的原理、冲突管理 12.git 与 svn 的区别13.设置alias别名…...

Seaborn:数据可视化的强大工具

文章目录 引言Seaborn的原理1. 底层结构2. 数据集成3. 图形类型 Seaborn的使用1. 安装与导入2. 数据加载与探索3. 绘制图形分布图关系图分类图 4. 图形定制5. 导出图形 结论 引言 在数据分析和科学计算领域&#xff0c;数据可视化是一个至关重要的步骤。它能够帮助我们更直观地…...

图解注意力

图解注意力 Part #2: The Illustrated Self-Attention 在文章前面的部分&#xff0c;我们展示了这张图片来展示自注意力被应用于正在处理单词"it"的一层中&#xff1a; 在本节中&#xff0c;我们将看看这是如何完成的。请注意&#xff0c;我们将以一种试图理解单…...

Typora Markdown编辑器 for Mac v1.8.10 安装

Mac分享吧 文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2. 应用程序显示软件图标&#xff0c;表示安装成功 三、运行调试1、修改主题2、显示文档列表&#xff0c;如下图3、查看版本信息 **安装完成&…...

代码随想录算法训练营Day46|动态规划:121.买卖股票的最佳时机I、122.买卖股票的最佳时机II、123.买卖股票的最佳时机III

买卖股票的最佳时机I 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 之前用贪心算法做过相同的题&#xff0c;这次考虑使用动态规划来完成。 dp[i]表示前i天的最大利润 我们已知每一天的价格price[i]&#xff0c;则dp[i]为每一天的价格price[i]减去当初…...

hive on spark 记录

环境&#xff1a; hadoop 2.7.2 spark-without-hadoop 2.4.6 hive 2.3.4 hive-site.xml <property><name>hive.execution.engine</name><value>spark</value> </property> <property><name>spark.yarn.jars</name>&l…...

【计算机网络体系结构】计算机网络体系结构实验-DHCP实验

服务器ip地址 2. 服务器地址池 3. 客户端ip 4. ping Ipconfig...

攻防世界-pdf

方法一&#xff1a;打开是pdf格式的文件&#xff0c;里面有一张图&#xff0c;题目提示图下面什么都没有&#xff1f;emmm用chrom打开pdf——ctrlf搜索flag&#xff0c;里面是有东西的&#xff0c;ctrla复制就可以了。 方法二&#xff1a;题目提示图下面什么都没有&#xff0c;…...

关于后端幂等性问题分析与总结

后端幂等性&#xff08;Idempotency&#xff09;是指对系统执行一次操作或多次执行相同的操作&#xff0c;其结果始终如一。在分布式系统和API设计中&#xff0c;这是一个关键概念&#xff0c;因为它能保证用户无论请求被路由到哪个节点&#xff0c;多次执行相同的请求都不会导…...

2024广东省职业技能大赛云计算赛项实战——容器云平台搭建

容器云平台搭建 前言 容器镜像使用的是斗学培训平台提供的镜像包&#xff0c;这东西网上都没有&#xff0c;一堆人要&#xff0c;我是靠自己想的方法获取到了&#xff0c;也不敢给。你们可以通过在这个网站申请环境进行操作https://ncc.douxuedu.com/ 虚拟机使用的是自行创建…...

手持弹幕LED滚动字幕屏夜店表白手灯接机微信抖音小程序开源版开发

手持弹幕LED滚动字幕屏夜店表白手灯接机微信抖音小程序开源版开发 专业版 插件版 手持弹幕小程序通常提供多种功能&#xff0c;以便用户在不同的场合如夜店、表白、接机等使用。以下是一些常见的功能列表&#xff1a; 文本输入&#xff1a; 输入要显示的文字内容&#xff0c;…...

红队内网攻防渗透:内网渗透之内网对抗:代理通讯篇无外网或不可达SockS全协议规则配置C2正反向上线解决方案

红队内网攻防渗透 1. 内网代理通讯1.1 网络不可达实战环境模拟1.1.1 CS代理技术-SockS配置-网络不可达-通讯解决1.1.1.1 反向shell上线入口点主机1.1.1.2 入口点CS搭建sokcs4代理1.1.1.3 本地使用Proxifier访问代理1.1.1 CS代理技术-正反向监听-网络不可达-C2上线1.1.1.4 正向s…...

PHP学习总结-入门篇

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

IDEA Plugins中搜索不到插件解决办法

IDEA中搜不到插件有三种解决方案&#xff1a; 设置HTTP选项&#xff0c;可以通过File->Settings->Plugins->⚙->HTTP Proxy Settings进行设置 具体可参考这篇博文&#xff1a;IDEA Plugins中搜索不到插件解决办法本地安装&#xff0c;ile->Settings->Plugin…...