知识库首页 模版 use-toast.ts.md

use toast.ts

本地来源:模版/模版文档对比说明/06-工具函数/use-toast.ts.md

提示钩子 (hooks/use-toast.ts) 逐行分析

文件概述

这是一个复杂的自定义 React Hook,实现了全局的 Toast 通知系统。它使用 Reducer 模式管理状态,支持多种类型的通知消息,并提供了完整的生命周期管理。

导入语句分析

"use client";
  • 指示这是一个客户端组件
  • 确保在浏览器环境中执行
import * as React from "react";
  • 导入 React 的所有导出
  • 使用命名空间导入的方式
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
  • 导入 Toast 组件的类型定义
  • 使用 type 关键字表示只导入类型

常量定义分析

const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
  • TOAST_LIMIT: 限制同时显示的 Toast 数量为 1
  • TOAST_REMOVE_DELAY: 设置 Toast 移除延迟时间(非常长的延迟,实际由用户操作控制)

类型定义分析

ToasterToast 类型

type ToasterToast = ToastProps & {
  id: string;
  title?: React.ReactNode;
  description?: React.ReactNode;
  action?: ToastActionElement;
};
  • 继承基础 ToastProps
  • 添加唯一 id 标识
  • 可选的标题和描述(支持 React 节点)
  • 可选的操作按钮

Action 类型系统

const actionTypes = {
  ADD_TOAST: "ADD_TOAST",
  UPDATE_TOAST: "UPDATE_TOAST",
  DISMISS_TOAST: "DISMISS_TOAST",
  REMOVE_TOAST: "REMOVE_TOAST",
} as const;
  • 定义 Action 类型常量
  • 使用 as const 确保类型推断精确
type ActionType = typeof actionTypes;
  • 从常量对象推断类型
type Action =
  | {
      type: ActionType["ADD_TOAST"];
      toast: ToasterToast;
    }
  | {
      type: ActionType["UPDATE_TOAST"];
      toast: Partial<ToasterToast>;
    }
  | {
      type: ActionType["DISMISS_TOAST"];
      toastId?: ToasterToast["id"];
    }
  | {
      type: ActionType["REMOVE_TOAST"];
      toastId?: ToasterToast["id"];
    };
  • 定义联合类型的 Action
  • 每种操作对应不同的数据结构
  • 使用 Partial<T> 表示部分更新

State 接口

interface State {
  toasts: ToasterToast[];
}
  • 定义 Toast 状态接口
  • 包含 Toast 数组

工具函数分析

ID 生成器

let count = 0;

function genId() {
  count = (count + 1) % Number.MAX_VALUE;
  return count.toString();
}
  • 使用简单的计数器生成 ID
  • 防止数值溢出,使用取模运算
  • 转换为字符串类型

超时管理

const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();

const addToRemoveQueue = (toastId: string) => {
  if (toastTimeouts.has(toastId)) {
    return;
  }

  const timeout = setTimeout(() => {
    toastTimeouts.delete(toastId);
    dispatch({
      type: "REMOVE_TOAST",
      toastId: toastId,
    });
  }, TOAST_REMOVE_DELAY);

  toastTimeouts.set(toastId, timeout);
};
  • 使用 Map 管理超时器
  • 防止重复添加相同 ID 的超时器
  • 延迟移除 Toast,实现淡出效果
  • 自动清理超时器引用

Reducer 函数分析

export const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case "ADD_TOAST":
      return {
        ...state,
        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
      };
    // ... 其他 case
  }
};

ADD_TOAST 处理

case "ADD_TOAST":
  return {
    ...state,
    toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
  };
  • 新 Toast 添加到数组开头
  • 使用 slice 限制数量
  • 保持状态不可变性

UPDATE_TOAST 处理

case "UPDATE_TOAST":
  return {
    ...state,
    toasts: state.toasts.map((t) =>
      t.id === action.toast.id ? { ...t, ...action.toast } : t
    ),
  };
  • 根据 ID 更新特定 Toast
  • 使用对象展开合并属性
  • 保持其他 Toast 不变

DISMISS_TOAST 处理

case "DISMISS_TOAST": {
  const { toastId } = action;

  // ! Side effects ! - This could be extracted into a dismissToast() action,
  // but I'll keep it here for simplicity
  if (toastId) {
    addToRemoveQueue(toastId);
  } else {
    state.toasts.forEach((toast) => {
      addToRemoveQueue(toast.id);
    });
  }

  return {
    ...state,
    toasts: state.toasts.map((t) =>
      t.id === toastId || toastId === undefined
        ? {
            ...t,
            open: false,
          }
        : t
    ),
  };
}
  • 处理 Toast 关闭请求
  • 可以关闭特定 Toast 或全部
  • 设置 open 状态为 false
  • 添加到移除队列以延迟删除

REMOVE_TOAST 处理

case "REMOVE_TOAST":
  if (action.toastId === undefined) {
    return {
      ...state,
      toasts: [],
    };
  }
  return {
    ...state,
    toasts: state.toasts.filter((t) => t.id !== action.toastId),
  };
  • 从状态中彻底移除 Toast
  • 支持移除特定 Toast 或清空全部
  • 使用 filter 创建新数组

全局状态管理分析

状态管理

const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] };

function dispatch(action: Action) {
  memoryState = reducer(memoryState, action);
  listeners.forEach((listener) => {
    listener(memoryState);
  });
}
  • 使用观察者模式管理全局状态
  • 内存中维护状态(不是组件状态)
  • 状态变化时通知所有监听器

Toast 函数

type Toast = Omit<ToasterToast, "id">;

function toast({ ...props }: Toast) {
  const id = genId();

  const update = (props: ToasterToast) =>
    dispatch({
      type: "UPDATE_TOAST",
      toast: { ...props, id },
    });
  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });

  dispatch({
    type: "ADD_TOAST",
    toast: {
      ...props,
      id,
      open: true,
      onOpenChange: (open) => {
        if (!open) dismiss();
      },
    },
  });

  return {
    id: id,
    dismiss,
    update,
  };
}
  • 创建新的 Toast 实例
  • 生成唯一 ID
  • 提供更新和关闭方法
  • 自动处理 open 状态变化
  • 返回控制方法供外部使用

useToast Hook 分析

function useToast() {
  const [state, setState] = React.useState<State>(memoryState);

  React.useEffect(() => {
    listeners.push(setState);
    return () => {
      const index = listeners.indexOf(setState);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    };
  }, [state]);

  return {
    ...state,
    toast,
    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
  };
}

状态同步

const [state, setState] = React.useState<State>(memoryState);
  • 使用组件本地状态同步全局状态
  • 初始化为当前全局状态

监听器管理

React.useEffect(() => {
  listeners.push(setState);
  return () => {
    const index = listeners.indexOf(setState);
    if (index > -1) {
      listeners.splice(index, 1);
    }
  };
}, [state]);
  • 注册状态变化监听器
  • 组件卸载时清理监听器
  • 防止内存泄漏

返回值

return {
  ...state,
  toast,
  dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
};
  • 展开状态对象
  • 提供 toast 创建函数
  • 提供 dismiss 关闭函数

设计模式分析

Reducer 模式

  • 使用 Reducer 管理复杂状态逻辑
  • 集中处理状态更新
  • 便于测试和调试

观察者模式

  • 全局状态变化通知所有监听器
  • 解耦状态管理和组件
  • 支持多个组件同时使用

单例模式

  • 全局唯一的状态管理实例
  • 跨组件共享状态
  • 统一的状态更新入口

工厂模式

  • toast 函数创建 Toast 实例
  • 封装创建逻辑
  • 提供统一的创建接口

最佳实践总结

  1. 状态管理:使用 Reducer 模式管理复杂状态
  2. 全局状态:使用观察者模式实现全局状态共享
  3. 内存管理:正确清理监听器和超时器
  4. 类型安全:完整的 TypeScript 类型定义
  5. 用户体验:提供丰富的 Toast 控制方法
  6. 性能优化:限制 Toast 数量,避免性能问题
  7. 可扩展性:支持自定义 Toast 内容和操作

使用示例

function MyComponent() {
  const { toast } = useToast();

  const showToast = () => {
    toast({
      title: "Success",
      description: "Operation completed successfully",
    });
  };

  return <button onClick={showToast}>Show Toast</button>;
}

这个 Hook 实现了一个完整的全局 Toast 通知系统,展示了高级的状态管理技术和设计模式的应用。它是构建用户交互反馈的重要工具。

本文档为站内渲染。原始文件本地路径:saas/source/templates/模版-模版文档对比说明-06-工具函数-use-toast-ts-2afa2f.md(仅本地保留,不入库不部署)