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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2025计算机毕业设计选题题目推荐-毕设题目汇总大全

选题在于精&#xff0c;以下是推荐的容易答辩的选题&#xff1a; SpringBoot Vue选题: 基于SpringBoot Vue家政服务系统 基于SpringBoot Vue非物质文化遗产数字化传承 基于SpringBoot Vue兽医站管理系统 基于SpringBoot Vue毕业设计选题管理系统 基于SpringBoot Vue灾害应急救援…

使用Ollama+OpenWebUI本地部署阿里通义千问Qwen2 AI大模型

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; &#x1f916;Ollama部署LLM专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月17日22点50分 &#x1f004;️文章质量&#xff…

safari浏览器无法连接到服务器

问题&#xff1a;MacBook pro&#xff0c;网络连接正常&#xff0c;可以使用各种软件上网&#xff0c;唯独safari浏览器打不开网页&#xff0c;报错说Safari无法连接到服务器&#xff1b; 原因&#xff1a;使用了VPN&#xff0c;VPN自动更改了网络设置&#xff0c;导致Safari浏…

数据结构-十大排序算法集合(四万字精讲集合)

前言 1&#xff0c;数据结构排序篇章是一个大的工程&#xff0c;这里是一个总结篇章&#xff0c;配备动图和过程详解&#xff0c;从难到易逐步解析。 2&#xff0c;这里我们详细分析几个具备教学意义和实际使用意义的排序&#xff1a; 冒泡排序&#xff0c;选择排序&#xff0c…

【vue大作业-端午节主题网站】【预览展示视频和详细文档】

vue大作业-端午节主题网站介绍 端午节&#xff0c;又称为龙舟节&#xff0c;是中国的传统节日之一&#xff0c;每年农历五月初五庆祝。这个节日不仅是纪念古代爱国诗人屈原的日子&#xff0c;也是家人团聚、共享美食的时刻。今天&#xff0c;我们非常高兴地分享一个以端午节为…

建筑学跑路:揭秘热门转行新选择!

话说建筑学真的是我见过最关心同行的专业&#xff0c;每个建筑学跑路的帖子下面都有人问&#xff1a;你跑哪里去了&#xff1f; 很多人表示&#xff0c;我也想跑 当然不仅建筑学&#xff0c;园林的、城规的、土木的也会来凑热闹&#xff1a; 很多小伙伴分享了自己的转行经历&a…

用这个神级提示词插件,能让你的AI绘画工具Stable diffusion提示词直接写中文!

大家好&#xff0c;我是设计师阿威 最近&#xff0c;有同学在使用AI绘画工具 Stable Diffusion的时候和我说&#xff1a;老师&#xff0c;我英文不好&#xff0c;能不能直接让我写中文提示词啊&#xff1f;最好可以直接在SD的输入框就能直接写中文&#xff0c;不用切换网页或者…

哪个充电宝牌子好用又实惠?盘点四大平价充电宝分享

在当今快节奏的生活中&#xff0c;充电宝已成为我们日常生活中不可或缺的一部分。然而&#xff0c;面对市场上琳琅满目的充电宝品牌和型号&#xff0c;许多消费者误以为选择容量越大、价格越高的充电宝就是最好的选择。实际上&#xff0c;买充电宝并不是一味追求高容量和高价格…

好用的加密软件谁在用啊?不会吧,还不知道迅软DSE加密软件好用?

说起加密软件想必大家都不陌生&#xff0c;就是用来保护机密数据的&#xff0c;防止泄密行为的出现&#xff0c;那么重点来了&#xff0c;想要完全的把机密信息保护起来&#xff0c;那就用到了加密软件&#xff0c;需要选择一款靠谱、有效果的加密软件才能实现加密&#xff0c;…

2024信息系统、信号处理与通信技术国际会议(ICISPCT2024)

2024信息系统、信号处理与通信技术国际会议&#xff08;ICISPCT2024) 会议简介 2024国际信息系统、信号处理与通信技术大会&#xff08;ICISPCT2024&#xff09;将在青岛隆重开幕。本次会议旨在汇聚全球信息系统、信号处理和通信技术领域的专家学者&#xff0c;共同探索行业…

希尔排序-C语言版本

前言 从希尔开始&#xff0c;排序的速度就开始上升了&#xff0c;这里的排序开始上一个难度了&#xff0c;当然难一点的排序其实也不是很难&#xff0c;当你对于插入排序了解的足够深入的时候&#xff0c;你会发现其实希尔就是插入的异形&#xff0c;但是本质上还是一样的 希尔…

解决linux下载github项目下载不下来,下载失败, 连接失败的问题

第一步&#xff1a;打开/etc/hosts文件 linux vim /etc/hosts 第二步&#xff1a;文件拉到最下面&#xff0c;输入以下内容 linux #GitHub Start 140.82.113.3 github.com 140.82.114.20 gist.github.com 151.101.184.133 assets-cdn.github.com 151.101.184.133 raw.githubus…

在有限的分数有限下如何抉择?是选好专业还是选好学校

随着2024年高考的落幕&#xff0c;无数考生和家长站在了人生的重要十字路口。面对成绩单上的数字&#xff0c;一个难题摆在了面前&#xff1a;在分数限制下我们该如何平衡“心仪的专业”与“知名度更高的学校”之间的选择&#xff1f; 一、专业决定未来职业走向 选择一个好的专…

PostgreSQL源码分析——initdb

数据库初始化 在安装完数据库后&#xff0c;需要进行初始化数据库操作&#xff0c;对应PostgreSQL数据库中就是需要进行initdb后&#xff0c;才能对数据库进行启动。initdb的过程&#xff0c;其实就是创建数据库实例的过程&#xff0c;生成模板数据库和相应的目录、文件信息&a…

vue大作业-端午节主题网站

vue大作业-端午节主题网站介绍 端午节&#xff0c;又称为龙舟节&#xff0c;是中国的传统节日之一&#xff0c;每年农历五月初五庆祝。这个节日不仅是纪念古代爱国诗人屈原的日子&#xff0c;也是家人团聚、共享美食的时刻。今天&#xff0c;我们非常高兴地分享一个以端午节为…

如何完美解决 Oracle Database 19c 安装程序 - 第7步(共8步)卡住,半小时都不动

&#x1f680; 如何完美解决 Oracle Database 19c 安装程序 - 第7步&#xff08;共8步&#xff09;卡住&#xff0c;半小时都不动 摘要 在安装 Oracle Database 19c 时&#xff0c;很多用户会在第7步&#xff08;共8步&#xff09;遇到卡住的问题&#xff0c;尤其是安装程序长…

【html】用html5+css3+JavaScript制作一个计数器

目录 简介&#xff1a; 效果图&#xff1a; 源码&#xff1a; html: CSS: JS: 源码解析&#xff1a; 简介&#xff1a; 在日常生活当中很多事情都需要用到计数器特别是在体育运动当中&#xff0c;可以我们那么我们可不可以通过网页来制作一个计数器呢答案是肯定的我们需要利…

【Python】Redis数据库

Redis数据库 Unit01一、Redis1.1 概述1.2 安装1.3 Redis-cli1.4 数据类型1.5 字符处理1.6 键的命名规则 二、通用命令三、字符串(String)3.1 概述3.2 常用命令3.3 应用场景 四、列表(List)4.1 概述4.2 常用命令 五、集合(SET)5.1 概述5.3 常用命令 六、有序集合6.1 概述6.2 常用…

智慧养老,乐享晚年 — 探索新时代的养老模式

​随着科技的飞速发展和人口老龄化趋势的加剧&#xff0c;传统的养老模式已经无法满足现代社会的需求。人们期待在晚年能够享受到更加智能、便捷、舒适的生活。智慧养老&#xff0c;作为一种融合现代科技与养老服务的新型模式&#xff0c;正逐渐成为时代的选择&#xff0c;为老…

java第二十五课 —— 多态

多态 传统的方法带来的问题是什么?如何解决&#xff1f;问题是&#xff1a;代码的复用性不高&#xff0c;而且不利于代码维护。 未使用多态时候的例子&#xff1a; Poly01.java&#xff1a; package com.hspedu.poly_;public class Poly01 {public static void main(Strin…