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

news2024/11/24 5:38:40

文章目录

  • 前言
  • 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: string
  type: 'success' | 'warning' | 'danger'
  duration?: number
  id: 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 = callback
    savedClearCallback.current = clearCallback
  })

  // 在组件挂载和卸载时设置定时器
  useEffect(() => {
    // 定义定时器的回调函数
    const tick = () => {
      // 执行保存的回调函数
      savedCallback.current()
    }

    // 设置定时器,执行tick函数,并返回定时器的ID
    const 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)) : state
      globalState.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 }) => {
  // 初始化透明度为1
  const [opacity, setOpacity] = useState(1)
  let animationId: number = 0
  // 定义一个定时器,在指定时间后执行 willLeave 函数,实现 toast 消息的逐渐消失效果
  useTimeoutWithUnmount(
    () => {
      const requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame
      let 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.webkitCancelAnimationFrame
        cancelAnimationFrame(animationId)
      }
    },
    data.duration || DEFAULT_TOAST_DURATION,
  )

  // 渲染 toast 消息
  return (
    <ToastItemPanel
      style={{
        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 函数将其从状态中移除
          <ToastItem
            willLeave={() => {
              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.coloradmin.cn/o/1849943.html

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

相关文章

Java 图书管理系统功能实现

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

安装CDH时报错:Parcel 不可用于操作系统分配 RHEL7,原因与解决办法~

报错信息&#xff1a; 解决办法与思路&#xff1a; 1、检查CDH包的后缀名称&#xff0c;Redhat与Centos安装时不需要修改后缀名称&#xff0c;麒麟系统安装时才需要修改。 2、目录里面需要有xxx.parcel xxx.parcel.sha manifest.json 三个文件 缺一不可&#xff08;注&#x…

储能电池竞争出海分析

锂电池的激烈竞争进一步蔓延到储能行业。为保市场份额和现金流稳定&#xff0c;不少储能电池企业都开始大幅度降低报价只求中标储能项目。 随着6月的储能电芯的最高限价和系统报价都已经贴近成本价&#xff0c;一二三线的储能电池厂商将要如何应对&#xff1f; 1、储能规模快速…

前端核心框架Vue指令详解

目录 ▐ 关于Vue指令的介绍 ▐ v-text与v-html ▐ v-on ▐ v-model ▐ v-show与v-if ▐ v-bind ▐ v-for ▐ 前言&#xff1a;在学习Vue框架过程中&#xff0c;大家一定要多参考官方API &#xff01; Vue2官方网址https://v2.cn.vuejs.org/v2/guide/ ▐ 关于Vue指令的…

第五篇:构建与维护私有Docker Registry: 企业级实践指南

构建与维护私有Docker Registry: 企业级实践指南 1. 引言&#xff1a;解析私有Docker仓库的必要性 1.1 Docker Registry简介与私有化的好处 Docker Registry是一个用于存储和分发Docker镜像的系统。在Docker生态系统中&#xff0c;Registry扮演着至关重要的角色&#xff0c;为…

MySQL 面试突击指南:核心知识点解析2

事务并发可能引发的问题 MySQL 是一个客户端/服务器架构的软件,对于同一个服务器来说,可以有多个客户端与之连接,每个客户端与服务器连接后,可以称为一个会话(Session)。每个客户端都可以在自己的会话中向服务器发出请求语句,一个请求语句可能是某个事务的一部分,也就…

DPDK的Cache预取和Cache一致性

1.什么是Cache预取 众所周知&#xff0c;CPU访问Cache中的数据是比访问内存中的数据是要快的&#xff0c;而因为程序都有时间局部性和空间局部性&#xff0c;时间局部性简单来说就是某一条或几条指令在一段时间内会被CPU多次执行&#xff1b;空间局部性简单来说就是某一段数据块…

Electron快速入门(三):在(二)的基础上修改了一个文件夹做了个备忘录

Lingering Memories 诗绪萦怀 修改index.html <!--index.html--> <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --><meta http…

ES数值类型慢查询优化

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

FlinkSQL开发经验分享

最近做了几个实时数据开发需求&#xff0c;也不可避免地在使用Flink的过程中遇到了一些问题&#xff0c;比如数据倾斜导致的反压、interval join、开窗导致的水位线失效等问题&#xff0c;通过思考并解决这些问题&#xff0c;加深了我对Flink原理与机制的理解&#xff0c;因此将…

Ubuntu配置ssh+vnc(完整版)

Ubuntu配置sshvnc&#xff08;完整版&#xff09; 1 配置ssh 1. 安装openssh-server&#xff0c;配置开机自启 # 更新包 sudo apt-get update # 安装openssh-server sudo apt-get install -y openssh-server # 启动服务 sudo service ssh start # 配置开机自启 sudo systemc…

corepack管理包管理器;nvm管理node版本;nrm管理npm源地址

corepack corepack 管理"包管理器"&#xff0c;包括 yarn 和 pnpm。corepack 并不能管理 npm。 corepack 是 nodejs 提供的功能&#xff0c;安装 nodejs 时 corepack 就一起安装了。它还是实验性功能&#xff0c;默认是关闭的&#xff0c;具体介绍看官方文档。 注…

DevOps学习回顾01-技能发展路线-岗位能力-体系认知

事为先&#xff0c;人为重–事在人为 参考来源&#xff1a; 极客时间专栏&#xff1a;DevOps实战笔记&#xff0c;作者&#xff1a;石雪峰 课程链接&#xff1a;https://time.geekbang.org/column/intro/235 时代的典型特征 VUCA VUCA 是指易变性&#xff08;Volatility&…

高性能并行计算华为云实验一:MPI矩阵运算

目录 一、实验目的 二、实验说明 三、实验过程 3.1 创建矩阵乘法源码 3.1.1 实验说明 3.1.2 实验步骤 3.2 创建卷积和池化操作源码 3.2.1 实验说明 3.2.2 实验步骤 3.3 创建Makefile文件并完成编译 3.4 建立主机配置文件与运行监测 四、实验结果与分析 4.1 矩阵乘法…

qt 简单实验 一个可以向右侧拖拽缩放的矩形

1.概要 目的是设置一个可以拖拽缩放的矩形&#xff0c;这里仅用右侧的一个边模拟这个过程。就是为了抓住核心&#xff0c;这个便解决了&#xff0c;其他的边也是一样的。而这个更能体现原理。 2.代码 2.1 resizablerectangle.h #ifndef RESIZABLERECTANGLE_H #define RESIZ…

Linux驱动调试——使用DEVICE_ATTR实现cat、echo指令调试驱动

在平常做一些驱动调试的时候&#xff0c;每次都写应用去调试相对较麻烦&#xff0c;有一个非常便捷的操作方法就是使用device_attr&#xff0c;只需要执行shell指令例如echo和cat就可以看到效果&#xff0c;不需要再单独写一个测试demo。 看网上很多博客在这一块的使用上写的都…

RK3568平台(音频篇)RT5651解码芯片Codec驱动分析

一.Audio Codec的必要性 在理想状况下&#xff0c;对于录音过程&#xff0c;只需要将麦克风获取到的analog信号通过ADC转换为digital信号并存储即可&#xff0c;对于播放音过程&#xff0c;只需要将digital信号通过DAC转换为analog并输出到speaker播放即可。 但在实际的过程中…

C++ 编程技巧分享

侯捷 C 学习路径&#xff1a;面向对象的高级编程 -> STL库 -> C11新特性 -> cmake 1.1. C 与 C的区别 在C语言中&#xff0c;主要存在两大类内容&#xff0c;数据和处理数据的函数&#xff0c;二者彼此分离&#xff0c;是多对多的关系。不同的函数可以调用同一个数据…

Docker开机自动重启及自动启动容器

Docker开机自动重启及自动启动容器 Windows开机自动重启设置容器自动启动 Windows开机自动重启 勾选 Start Docker Desktop when you sign in to your computer 设置容器自动启动 1.docker update 命令 Usage: docker update [OPTIONS] CONTAINER [CONTAINER...]Update co…

32.基于分隔符解决黏包和半包

LineBasedFrameDecoder 基于换行/n (linux)或回车换行/r/n(windows)进行分割。 使用LIneBasedFrameDecoder构造方法,需要设定一个最大长度。 如果超过了最大长度,还是没有找到换行符,就这位这个数据段太长了,抛出ToolLongFrameException DelimiterBasedFrameDecoder …