React 用 Portal + Context 实现全局 Toast:一次调用、自动消失与队列管理
React 用 Portal Context 实现全局 Toast:一次调用、自动消失与队列管理做后台系统时,几乎每个页面都要弹提示:保存成功、网络错误、权限不足。如果每个页面都自己维护一个visible状态、写一段setTimeout关闭,重复代码满天飞,还经常出现「多个提示叠在一起互相覆盖」的 bug。我们要的效果很朴素:任何组件里,一行toast.success(保存成功)就能在页面右上角弹出提示,3 秒后自己消失,多个提示自动排队叠放。这篇就从零把它搭出来,顺带讲清 Portal 和 Context 各自解决的到底是什么问题。朴素写法的坑:提示被 overflow 裁掉了先看很多人第一版会怎么写——在页面组件里放一个提示 div:function Page() { const [msg, setMsg] useState(); return ( div style{{ overflow: hidden }} {/* 一堆内容 */} {msg div classNametoast{msg}/div} /div ); }两个问题立刻暴露:提示 div 是页面 DOM 的子节点,一旦某个祖先设了overflow: hidden或transform,提示要么被裁掉一半,要么定位基准跑偏,position: fixed都不一定救得回来。每个页面都要自己维护msg状态和关闭逻辑,换个页面就得重写一遍。第一个问题交给Portal:把提示的真实 DOM 渲染到body下,彻底逃出祖先的overflow和层叠上下文。第二个问题交给Context:把「弹提示」这个能力做成全局服务,任何组件useToast()就能拿到。第一步:用 Context 承载 toast 队列先定义状态形状。每条 toast 有唯一 id(用来精确移除)、类型和文案:import { createContext, useCallback, useContext, useRef, useState } from react; const ToastContext createContext(null); let seed 0; // 模块级自增,保证 id 全局唯一,不依赖数组长度 export function ToastProvider({ children }) { const [toasts, setToasts] useState([]); const remove useCallback((id) { setToasts((list) list.filter((t) t.id ! id)); }, []); const push useCallback((type, message, duration 3000) { const id seed; setToasts((list) [...list, { id, type, message }]); // 到点自动移除;返回 id 方便调用方提前手动关 setTimeout(() remove(id), duration); return id; }, [remove]); return ( ToastContext.Provider value{push} {children} ToastViewport toasts{toasts} onClose{remove} / /ToastContext.Provider ); }这里有个容易踩的点:id 千万别用toasts.length或Date.now()。数组长度会因为删除而重复;Date.now()在同一毫秒内连发两条会撞车。用一个模块级自增变量最稳。第二步:用 Portal 把视图渲染到 bodyToastViewport负责把所有提示画到屏幕右上角。关键就是createPortal——它让 React 组件的 DOM 挂到指定容器,但事件冒泡、Context 读取仍按 React 组件树走,而不是按真实 DOM 位置走。import { createPortal } from react-dom; function ToastViewport({ toasts, onClose }) { // SSR 阶段没有 document,直接不渲染,避免 hydration 报错 if (typeof document undefined) return null; return createPortal( div style{{ position: fixed, top: 16, right: 16, display: flex, flexDirection: column, gap: 8, zIndex: 9999, }} {toasts.map((t) ( div key{t.id} onClick{() onClose(t.id)} style{{ padding: 10px 16px, borderRadius: 6, color: #fff, cursor: pointer, background: t.type error ? #e5484d : #30a46c, boxShadow: 0 4px 12px rgba(0,0,0,.15), }} {t.message} /div ))} /div, document.body // 挂到 body,逃出任何祖先的 overflow/transform ); }多个提示天然「排队叠放」,因为它们只是 flex 列里的兄弟节点,新的往下加,旧的到点自己删——队列管理不用额外写逻辑,数组本身就是队列。第三步:封装一个好用的 Hook直接暴露push(type, message)不够顺手,包一层语义化 API:export function useToast() { const push useContext(ToastContext); if (!push) { // 忘了套 Provider 时,给一个明确报错,而不是让 push 是 null 后面才崩 throw new Error(useToast 必须在 ToastProvider 内使用); } return { success: (msg, d) push(success, msg, d), error: (msg, d) push(error, msg, d), }; }那句throw很值:漏套 Provider 是高频错误,提前抛一句人话,比等到调用时报push is not a function好排查得多。在应用根部套上 Provider:function App() { return ( ToastProvider YourRoutes / /ToastProvider ); }业务里随手就能用:function SaveButton() { const toast useToast(); const onSave async () { try { await api.save(); toast.success(保存成功); } catch (e) { toast.error(保存失败: e.message); } }; return button onClick{onSave}保存/button; }一个隐蔽的性能坑:Provider 重渲染拖垮全页上面ToastContext.Provider value{push}传的是push,它被useCallback固定了引用,所以toasts变化时,消费useToast()的业务组件不会跟着重渲染——它们只依赖push,不依赖toasts。如果图省事把value{{ push, toasts }}一起传下去,那每弹一条提示,全站useToast()的组件都会重渲染。把「触发能力」和「列表数据」分开:能力给业务组件,数据只留给 viewport 自己消费。这也是 Context 拆分的通用原则——变化频繁的数据别和稳定的能力混在一个 value 里。小结Portal 解决「位置」:把提示 DOM 挂到body,逃出祖先的overflow: hidden、transform和z-index层叠陷阱,同时保持 React 树的事件与 Context 语义。Context 解决「触达」:把弹提示做成全局能力,任何组件一行调用,不用层层传 props。队列 数组:新提示 push 进数组,setTimeout到点按 id filter 掉,叠放和自动消失都是数组的自然行为。id 用模块级自增,别用 length 或时间戳;value 里分离能力与数据,避免全站无谓重渲染;SSR 下判空 document,避免 hydration 崩。一句话记忆:Portal 管「画在哪」,Context 管「谁能调」,数组本身就是那条队列。

相关新闻

[virtio](一):`hw/virtio/` 总览

[virtio](一):`hw/virtio/` 总览

这一组文章从 QEMU hw/virtio/ 入手,拆解 virtio 设备模型、virtqueue、通知机制、transport、vhost、迁移和调试路径。本篇先建立全局地图:hw/virtio/ 到底负责什么,读源码时应该从哪里开始。1. 为什么从 hw/virtio/ 开始 virtio 是虚拟化 I…

2026/10/14 18:11:13 阅读更多 →
SuperRDP完全指南:3步解锁Windows远程桌面多用户并发连接

SuperRDP完全指南:3步解锁Windows远程桌面多用户并发连接

SuperRDP完全指南:3步解锁Windows远程桌面多用户并发连接 【免费下载链接】SuperRDP Super RDPWrap 项目地址: https://gitcode.com/gh_mirrors/su/SuperRDP 你是否曾经因为Windows远程桌面只能让一个人连接而烦恼?或者作为Windows家庭版用户&…

2026/10/12 19:17:10 阅读更多 →
深度学习模型可复现性终极指南:从随机种子到GPU计算的确定性训练

深度学习模型可复现性终极指南:从随机种子到GPU计算的确定性训练

1. 项目概述:为什么固定了随机种子,模型结果依然“薛定谔”? 如果你在训练深度学习模型,尤其是像Transformer这类复杂架构时,大概率遇到过这个让人抓狂的问题:明明在代码开头设置了 torch.manual_seed(42)…

2026/10/13 11:38:49 阅读更多 →

最新新闻

律师智能办案系统有哪些法律ai推荐?先看你的年预算在哪一档

律师智能办案系统有哪些法律ai推荐?先看你的年预算在哪一档

这个问题被问得很多,但脱离预算谈推荐没有意义。同样一支团队,年预算五千和年预算十万,答案完全不同。按三档来说。第一档:年预算基本为零这一档的现实约束是"能用免费的先用免费"。可关注的:通义法睿&#…

2026/10/15 4:05:46 阅读更多 →
JetBrains Rider下载和安装教程

JetBrains Rider下载和安装教程

一、JetBrains Rider下载 官网下载地址:https://www.jetbrains.com.cn/rider/ 下载完成! 二、JetBrains Rider 安装 1. 双击.exe文件 2. 点击,下一步 3. 点击浏览可以修改安装地址,然后点击下一步 4. 勾选四个选项&#xff…

2026/10/15 4:05:46 阅读更多 →
成片缺来源栏,等于把返工留给上线|数字明星日更样本

成片缺来源栏,等于把返工留给上线|数字明星日更样本

视听链路里,生成式内容要看得见来源。来源栏不是片尾口号,是观众和平台都能核对的一栏。空着交付,上线后再补,往往比省下的剪辑时间更贵。日更组容易把「好看」当成完成。对采购来说,好看只是门槛;来源说得…

2026/10/15 4:05:46 阅读更多 →
碰碰好评卡全流程开发实战 从需求梳理到上线落地完整指南

碰碰好评卡全流程开发实战 从需求梳理到上线落地完整指南

碰碰好评卡全流程开发实战 从需求梳理到上线落地完整指南 碰碰好评卡是连接线下实体商家与C端用户的评价激励数字化工具,解决了传统纸质好评卡易丢失、核销难、数据无沉淀的痛点,可广泛应用于餐饮、美业、零售、生活服务等线下场景。本文将从需求梳理、架…

2026/10/15 4:05:46 阅读更多 →
MP2642GR-Z与PIC18F46K22两串电池主动均衡系统设计

MP2642GR-Z与PIC18F46K22两串电池主动均衡系统设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 4:05:46 阅读更多 →
双电机项目的学习记录1

双电机项目的学习记录1

cubemx里的配置问题,为什么这样配System Core → RCC → High Speed Clock (HSE) 选 Crystal/Ceramic Resonator所以才这样配置是为了精度更高-----------------------------------------------------------------------------------------------------------------…

2026/10/15 4:04:46 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →