Refine 中的 useNotification Hook 实战指南:open / close 与可撤销(Undoable)通知完整解析
2026/9/12 13:59:24 网站建设 项目流程

Refine 中的 useNotification Hook 实战指南:open / close 与可撤销(Undoable)通知完整解析

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

导读

useNotification是 Refine 框架中用于在应用任意位置弹出或关闭通知(Toast / Snackbar)的核心 Hook,它本身不关心 UI 实现,而是将openclose两个方法透传给你在<Refine>组件上配置的notificationProvider。本文以 basic-usage-live-preview.md 为主线,结合 index.md 与 notification-provider/index.md,先讲透useNotification的三种通知类型与可撤销流程,再下沉到源码层面解析其 Context 实现、OpenNotificationParams类型定义与内置 Provider 的落地方式,最后手把手带你从零实现一个基于react-toastify的自定义notificationProvider。读完本文,你将掌握在 Refine 项目中声明式地触发、更新、关闭通知,并实现"进度 + 撤销"交互的完整能力。

一、useNotification 是什么:一个只做透传的 Hook

useNotification的定位非常纯粹:在任何组件中获取openclose方法,用来随时打开或关闭通知。它本身不包含任何 UI 逻辑,而是从notificationProvider中取出这两个方法并原样返回。

这一点可以从源码得到直接印证。完整的实现只有 10 行:

// packages/core/src/hooks/notification/useNotification/index.ts import { useContext } from "react"; import { NotificationContext } from "@contexts/notification"; import type { INotificationContext } from "../../../contexts/notification/types"; export const useNotification = (): INotificationContext => { const { open, close } = useContext(NotificationContext); return { open, close }; };

它通过useContext读取NotificationContext。这个 Context 由NotificationContextProvider提供,默认值为空对象{}

// packages/core/src/contexts/notification/index.tsx export const NotificationContext = createContext<INotificationContext>({});

INotificationContext的类型定义(packages/core/src/contexts/notification/types.ts)同时给出了useNotification的完整返回签名:

export interface INotificationContext { open?: (params: OpenNotificationParams) => void; close?: (key: string) => void; } export type NotificationProvider = Required<INotificationContext>;

两个方法都是可选的(?),因此文档中的示例一律使用open?.(...)close?.(...)这种可选调用写法,即便项目没有配置任何通知 Provider 也不会抛错。

open / close 方法签名速查

属性用途类型
open打开(或更新)一条通知(params: OpenNotificationParams) => void
closekey关闭一条通知(key: string) => void

二、OpenNotificationParams:通知参数逐个拆解

open方法接收的唯一参数是OpenNotificationParams,其类型同样定义在 packages/core/src/contexts/notification/types.ts:

export type OpenNotificationParams = { key?: string; message: string; type: "success" | "error" | "progress"; description?: string; cancelMutation?: () => void; undoableTimeout?: number; };

各字段的含义与使用要点如下:

字段类型是否必填说明
keystring可选通知的唯一标识。必须传入,因为close方法正是靠它来关闭指定通知;同一个key再次open会更新已有通知而非新建
messagestring必填通知主标题/主要内容
type"success" \| "error" \| "progress"必填通知类型。前两者渲染普通提示;progress用于可撤销(undoable)通知
descriptionstring可选通知的补充描述(次要文本)
cancelMutation() => void可选撤销回调。用户点击 Undo 时执行,通常用于取消正在进行的数据变更
undoableTimeoutnumber可选可撤销通知的倒计时秒数,配合type: "progress"使用

值得强调的是key的语义:不传key时无法通过close关闭这条通知;而传入相同key再调open,则会在既有通知上执行更新(更新文案、进度等),而不是叠加弹出新通知。这在倒计时刷新进度、以及多次操作同一条数据时非常有用。

三、基础用法:三种通知的触发与关闭

下面这段代码来自 basic-usage-live-preview.md 的在线可运行示例(MUI 实现,包裹在RefineMuiDemo中),它一次性演示了successerrorprogress三种通知的触发:

import { useNotification } from "@refinedev/core"; import { Button, Stack } from "@mui/material"; const ExamplePage: React.FC = () => { const { open, close } = useNotification(); return ( <Stack spacing={2} direction="row"> <Button color="success" variant="outlined" size="small" onClick={() => open?.({ type: "success", message: "Success", description: "Success description", }) } > Success </Button> <Button color="error" variant="outlined" size="small" onClick={() => open?.({ type: "error", message: "Error", description: "Error description", }) } > Error </Button> <Button color="secondary" variant="outlined" size="small" onClick={() => open?.({ type: "progress", message: "Progress", undoableTimeout: 5, cancelMutation: () => { alert("cancelMutation"); }, }) } > Progress </Button> </Stack> ); };

打开普通通知

const { open } = useNotification(); open?.({ type: "success", message: "Success", description: "This is a success message", });
  • success/error两种类型对应普通提示,description作为补充说明展示在message之下;
  • 具体视觉样式完全取决于你接入的 UI 库(Ant Design、MUI、Mantine、Chakra UI 或自定义 Provider)。

按 key 关闭通知

const { close } = useNotification(); close?.("notification-key");

要能成功关闭,前提是open时传入了同一个key

open?.({ key: "notification-key", type: "success", message: "Success", });

完整参数说明可参考 接口参考文档 中的Open Notification Params一节(当前仓库文档树中对应documentation/docs/core/目录)。

四、可撤销(Undoable)通知:type 与倒计时机制

FAQ 中明确给出了可撤销通知的正确姿势:必须使用type: "progress",同时传入undoableTimeout(倒计时秒数)与cancelMutation(撤销回调):

const { open } = useNotification(); open?.({ type: "progress", message: "Progress", undoableTimeout: 5, cancelMutation: () => { // 当用户点击 Undo 按钮时,执行此回调 }, });

其运行机制可以概括为三点(依据 notification-provider/index.md):

  1. 每秒递减undoableTimeout从设定值开始,每 1 秒减 1,直到 0;
  2. 重复回调更新:倒计时期间,open方法会携带相同的key被反复调用,因此 Provider 必须用新值更新已有通知的进度条,而不是不断新建通知;
  3. 归零自动关闭:倒计时归零后通知自动关闭,此时数据变更正式生效;用户若在倒计时内点击 Undo,则执行cancelMutation撤销变更。

内置 Provider 中的倒计时实现

以 MUI 内置 Provider 为例,packages/mui/src/providers/notificationProvider/index.tsx 中type === "progress"的分支会:

  • 渲染CircularDeterminate圆形进度组件,传入undoableTimeoutmessage
  • 通过enqueueSnackbarautoHideDuration: (undoableTimeout ?? 0) * 1000设置自动关闭时长;
  • 在 Snackbar 的action中放置 Undo 按钮,点击后依次执行cancelMutation?.()closeSnackbar(key)关闭当前通知:
if (type === "progress") { const action = (key: any) => ( <IconButton onClick={() => { cancelMutation?.(); closeSnackbar(key); }} color="inherit" > <UndoOutlined /> </IconButton> ); enqueueSnackbar(<CircularDeterminate undoableTimeout={undoableTimeout ?? 0} message={message} />, { action, preventDuplicate: true, key, autoHideDuration: (undoableTimeout ?? 0) * 1000, }); }

五、背后机制:notificationProvider 与默认实现

useNotification返回的open/close最终来自<Refine>组件上配置的notificationProvider。从 packages/core/src/components/containers/refine/index.tsx 的源码可以看到它的注入路径:notificationProvider被归一化为一个函数并求值,其结果通过NotificationContextProvider注入 Context:

const useNotificationProviderValues = React.useMemo(() => { return typeof notificationProvider === "function" ? notificationProvider : () => notificationProvider; }, [notificationProvider]); const notificationProviderContextValues = useNotificationProviderValues(); // ... <NotificationContextProvider {...notificationProviderContextValues}>

notificationProvider本身是一个包含openclose两个方法的对象:

const notificationProvider = { open: () => {}, close: () => {}, };

其类型定义(packages/core/src/contexts/notification/types.ts)与上面展示的OpenNotificationParams完全对应:

interface NotificationProvider { open: (params: OpenNotificationParams) => void; close: (key: string) => void; }

默认行为:Refine 并不强制要求配置notificationProvider。未提供时,Context 默认值为空对象,应用可以在完全没有通知功能的情况下正常运行;只有当你的业务需要提示用户(尤其是配合undoable变更模式)时,才需要接入一个 Provider。

内置 Provider 一键接入

Refine 对主流 UI 库提供了开箱即用的useNotificationProvider

Ant Design@refinedev/antd):

import { useNotificationProvider } from "@refinedev/antd"; return ( <Refine //... notificationProvider={useNotificationProvider} /> );

Material UI@refinedev/mui,需包裹RefineSnackbarProvider):

import { useNotificationProvider, RefineSnackbarProvider } from "@refinedev/mui"; return ( <RefineSnackbarProvider> <Refine //... notificationProvider={useNotificationProvider} /> </RefineSnackbarProvider> );

Mantine@refinedev/mantine+@mantine/notificationsNotificationsProvider):

import { useNotificationProvider } from "@refinedev/mantine"; import { NotificationsProvider } from "@mantine/notifications"; return ( <NotificationsProvider position="top-right"> <Refine //... notificationProvider={useNotificationProvider} /> </NotificationsProvider> );

Chakra UI@refinedev/chakra-ui):

import { useNotificationProvider } from "@refinedev/chakra-ui"; return ( <Refine //... notificationProvider={useNotificationProvider()} /> );

六、从零实现自定义 notificationProvider(react-toastify 实战)

如果你希望完全掌控通知的视觉与交互,可以参照下面的思路,基于react-toastify从零构建一个 Provider。核心逻辑是把open收到的参数映射到 toast 的创建/更新,把close映射到 toast 的销毁。仓库中完整的可运行实现见示例项目 examples/with-react-toastify。

1. 安装并挂载 ToastContainer

首先在App中引入ToastContainer与样式文件:

import { Refine } from "@refinedev/core"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; const App: React.FC = () => { return ( <Refine /* ... */> {/* ... */} <ToastContainer /> </Refine> ); }; export default App;

2. 实现 open:创建与更新通知

基础版:把message展示为 toast,并用key作为toastId

import { toast } from "react-toastify"; const notificationProvider: NotificationProvider = { open: ({ message, key, type }) => { toast(message, { toastId: key, type, }); }, };

改进版:用toast.isActive(key)判断同key通知是否仍存在——存在则更新而非新建:

const notificationProvider: NotificationProvider = { open: ({ message, key, type }) => { if (toast.isActive(key)) { toast.update(key, { render: message, type, }); } else { toast(message, { toastId: key, type, }); } }, };

3. 支持 progress:可撤销通知的完整实现

当变更模式为undoable时,Refine 会发送type: "progress",同时携带cancelMutationundoableTimeout。如第四节所述,倒计时每秒触发一次open(同key),因此这里必须区分"首次创建"与"持续更新":

const notificationProvider: NotificationProvider = { open: ({ message, key, type }) => { if (type === "progress") { if (toast.isActive(key)) { toast.update(key, { progress: undoableTimeout && (undoableTimeout / 10) * 2, render: ( <UndoableNotification message={message} cancelMutation={cancelMutation} /> ), type: "default", }); } else { toast( <UndoableNotification message={message} cancelMutation={cancelMutation} />, { toastId: key, updateId: key, closeOnClick: false, closeButton: false, autoClose: false, progress: undoableTimeout && (undoableTimeout / 10) * 2, }, ); } } else { if (toast.isActive(key)) { toast.update(key, { render: message, closeButton: true, autoClose: 5000, type, }); } else { toast(message, { toastId: key, type, }); } } }, };

对应的UndoableNotification组件负责渲染"消息 + Undo 按钮",点击后先执行cancelMutation再关闭 toast:

type UndoableNotification = { message: string; cancelMutation?: () => void; closeToast?: () => void; }; export const UndoableNotification: React.FC<UndoableNotification> = ({ closeToast, cancelMutation, message, }) => { return ( <div> <p>{message}</p> <button onClick={() => { cancelMutation?.(); closeToast?.(); }} > Undo </button> </div> ); };

注意progress通知默认不可手动关闭(closeOnClick: falsecloseButton: false),以保证用户在倒计时结束前始终有机会点击 Undo;普通通知则显式加上closeButton: trueautoClose: 5000,让用户可以在进度结束后自行关闭。

4. 实现 close:按 key 销毁

close的职责非常单一:接收通知的key并销毁对应 toast:

const notificationProvider: NotificationProvider = { //... close: (key) => toast.dismiss(key), };

5. 在组件中消费

Provider 实现完毕后,即可通过useNotification在任意组件中使用openclose

import { useNotification } from "@refinedev/core"; const { open } = useNotification(); open?.({ type: "success", message: "Hey", description: "I <3 Refine", key: "unique-id", });
const { close } = useNotification(); close?.("displayed-notification-key");

七、与数据变更的联动:useHandleNotification

在 Refine 的实践中,通知并不只靠手动调用。open还承担着数据变更结果反馈的职责:createupdatedelete等 mutation 成功或失败时,会自动触发通知。这一联动由 packages/core/src/hooks/notification/useHandleNotification/index.ts 中的useHandleNotification完成:

import { useCallback } from "react"; import { useNotification } from "@hooks"; import type { OpenNotificationParams } from "../../../contexts/notification/types"; export const useHandleNotification = (): typeof handleNotification => { const { open } = useNotification(); const handleNotification = useCallback( (notification: OpenNotificationParams | false | undefined, fallbackNotification?: OpenNotificationParams) => { if (notification !== false) { if (notification) { open?.(notification); } else if (fallbackNotification) { open?.(fallbackNotification); } } }, [], ); return handleNotification; };

对应地,OpenNotificationParams派生出了SuccessErrorNotification类型(packages/core/src/contexts/notification/types.ts),mutation 相关的 hook 通过successNotification/errorNotification属性接受"通知参数、false(静默不提示)或返回这两者的函数":

export type SuccessErrorNotification<TData = unknown, TError = unknown, TVariables = unknown> = { successNotification?: | OpenNotificationParams | false | ((data?: TData, values?: TVariables, resource?: string) => OpenNotificationParams | false | undefined); errorNotification?: | OpenNotificationParams | false | ((error?: TError, values?: TVariables, resource?: string) => OpenNotificationParams | false | undefined); };

场景速览:把 useNotification 用进真实表单/表格

  • 保存表单成功提示useFormsuccessNotification配置为{ type: "success", message: "保存成功" },提交成功即自动弹出;
  • 删除可撤销:配合mutationMode="undoable"(参见 advanced-tutorials/mutation-mode.md),删除操作会先弹出一条progress通知并进入倒计时,用户可点击 Undo 取消删除;
  • 任意业务事件:在组件事件处理器中直接open?.({ type: "error", message: "...", description: "..." }),无需关心底层 UI 库。

八、结语与延伸阅读

回顾全文,useNotification的完整链路可以概括为:<Refine>接收notificationProvider→ 经NotificationContextProvider注入 Context →useNotification通过useContext取出open/close→ 应用任意位置触发、更新、关闭通知。type: "progress"配合undoableTimeoutcancelMutation构成了完整的可撤销交互闭环,而内置的各 UI 库 Provider(antd / mui / mantine / chakra)与自定义 Provider 只是open/close的不同落地实现。

如果你需要继续深入,建议阅读以下仓库资源:

  • notification-provider 完整文档:Provider 接口、内置实现与从零构建的完整代码
  • useNotification 类型定义:OpenNotificationParamsNotificationProviderSuccessErrorNotification的权威出处
  • MUI 内置 Provider 实现:查看 progress 通知与 Undo 按钮的完整渲染逻辑
  • refine 容器组件接入点:notificationProvider的注入与归一化
  • useHandleNotification 实现:mutation 结果自动通知的调用链
  • with-react-toastify 示例:完整可运行的自定义 Provider 工程

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询