跳到主要内容

Toast 轻提示

非阻塞反馈——3 秒后自动消失,居中或底部出现。

代码演示

下方在 Web 平台解析到 ToastHost.web.tsx:使用纯 React state、CSS transition 与 timer 驱动 fade + slide;native 才解析到 ToastHost.tsx 并使用 Reanimated 4。两端共享同一个 Toast Store 与 delivery identity 契约。每个 Button 都带真实的 onPress,点击即可触发对应提示。

视觉规范

来源:src/components/ui/Toast/styles.tsToastHost.tsxToastHost.web.tsxtoast.ts

元素规则
形态圆角 8(radius.mdc.inverseSurface 底(亮色 #1C1C1E),c.inverseOnSurface 白字
内边距横向 14(space[6])/ 纵向 10(space[4]
宽度max-width 85%,水平居中;距底部 32px(space[10]
字号14px(type.sm)/ 500
出现200ms(motion.base)fade + 8px slide-up
停留默认 3000ms(duration 可覆盖)
退出200ms fade + 下滑 8px
层级host position: absolute + zIndex: 200 + pointerEvents: none

状态变体

kind 仅决定文本左侧 6×6 圆点的颜色(来源:dotColorFor):

✓ success · c.success 绿点(亮色 #52C41A)
✕ error · c.error 红点(亮色 #F4511E)
ℹ info · 无圆点(默认)

用法

import { toast, ToastHost } from '@unif/react-native-design';

// 在应用根放一次 ToastHost
<ToastHost />;

// 任意位置触发
toast('已保存');
toast.success('订单提交成功');
toast.error('网络异常,请重试');
toast.info('已切换到日报模式');

// 自定义时长
toast({ message: '正在同步…', duration: 5000 });

API

来源:src/components/ui/Toast/toast.tstypes.tsToastHost.tsx

命令式函数 toast(不是组件):

调用签名说明
toast(input)(input: ToastInput) => void默认 kind: 'info'
toast.info(input)(input: ToastInput) => voidinfo 提示
toast.success(input)(input: ToastInput) => voidsuccess 提示(绿点)
toast.error(input)(input: ToastInput) => voiderror 提示(红点)

ToastInput = string(简写,走默认 kind + 3000ms)| 对象:

字段类型默认值说明
messagestring消息文本(必填)
kind'info' | 'success' | 'error''info'类型(决定圆点颜色)
durationnumber?3000自动消失毫秒数
position'top' | 'bottom' | 'center''bottom'显示位置(top/center 自动避让 safe-area)

<ToastHost /> 组件 props(ToastHostProps,在 app 根附近挂一个):

参数类型默认值说明
testIDstring?容器 testID;文本节点派生 ${testID}-text

全局挂一个 <ToastHost /> 即可;同一时间只显示一条,新调用替换旧的。要在 Modal 里显示 toast 时,可在 Modal 内再挂一个——它会接管,关闭后自动归还(见下方投递语义)。

投递语义

toast() 由一个纯状态机驱动(src/components/ui/Toast/store.ts),模型是 pending / delivery 双态:

场景行为
未挂 <ToastHost /> 时调用消息保留为 pending(不是丢弃、不告警);Host 挂上后立即补投
未挂 Host 时连续调用多次latest-wins —— 只保留最新一条,不排队补投历史消息
已挂 Host 时连续调用立即投递,后者替换前者;旧的那条的定时器 / 动画不再影响 UI
挂了多个 <ToastHost />栈式接管:最后挂载的收投递,前任入栈挂起并立刻收起自己那条 toast;接管者卸载后自动归还(乱序卸载也安全)
接管瞬间的在途 toast(有人挂到我上面)丢弃。它属于「已经被盖住的过去」,搬到接管者身上重放没有意义
归还瞬间的在途 toast(接管者卸载、我恢复)整条交回并立即重投。「Modal 内操作成功 → toast → 关窗」是最常见的路径,那条 toast 刚发出、用户一眼都还没看到
Host 卸载时消息还没显示完(栈里没有前任)未完成的投递退回 pending,下一个 Host 会重新投递;若期间已有更新的消息,更新的优先
Host 渲染 / 订阅回调抛错作废该 Host,消息退回 pending 等待新 Host
启动早期的 toast 不会丢

这是与旧版本的行为变更:此前未挂 Host 时 toast() 会告警并丢弃消息。现在消息会保留并在 Host 挂载后补投 —— 如果你依赖「没有 Host 就静默丢弃」,需要改为条件调用。

在自己的 Modal 里挂第二个 —— 这是受支持的用法

RN Modal独立的 native window,根 Host 渲染的 toast 会被它整块盖住(双端都看不见)。所以在 Modal 的内容树里再挂一个 <ToastHost />正解:它接管 owner,toast 渲染进 Modal 自己的 window;Modal 关闭、内层 Host 卸载后 owner 自动归还给根 Host,此时还没播完的那条会整条交回根 Host 重新播一遍(不追剩余时长)——所以「Modal 里点确定 → toast → 关窗」看到的是完整的 3 秒,不是被截断的尾巴。

接管以挂载顺序为准,不看层级。 谁最后挂载谁就是 owner —— 如果根子树被 re-key(切主题 / 切语言 / ErrorBoundary 重置)导致根 <ToastHost /> 重新挂载,它会从 Modal 内那个手里夺回 owner,直到 Modal 关闭为止 toast 又看不见了。别在 Modal 开着的时候 re-key 根。

这是与 0.24.x 及更早版本的行为变更:此前重复挂载的 <ToastHost /> 永久惰性(还会 dev warn),Modal 内自挂是死码。现在多 Host 是合法用法,告警已删除。

竞态守卫

每次投递带 owner token + leaseId + entry id 三重身份。Host 的每个定时器、RAF 和动画完成回调在改 UI 或上报完成前都要通过这三项 CAS —— 否则经典竞态会发生:A 的 3 秒定时器在 B 已经显示之后才触发,把 B 提前抹掉。

同一条消息被重新投递(Host 重挂)会拿到新的 leaseId,所以仅比较 entry.id 不足以区分两次投递。

ToastDelivery / lease / subscriber 等标识类型是 Host 与 Store 之间的内部协议,不从包根导出。ToastEntry.id 同样是内部身份,不保证跨版本稳定 —— 业务不要依赖它的具体取值。

无障碍(a11y)

来源:src/components/ui/Toast/ToastHost.tsxToastHost.web.tsxtoast.tstypes.ts

  • 默认 accessibilityRole。Toast 由 <ToastHost> 渲染为 <View> + <Text>(native 与 web 两份实现一致),源码设置 accessibilityRole
  • 朗读 / live region:<ToastHost> 在每条 toast 出现时调用 AccessibilityInfo.announceForAccessibility(message)(native 与 web 两端一致,web 经 RN-Web 注入 aria-live region),screen reader 会主动播报 message 文本。host 容器仍 pointerEvents="none"(不抢焦点)。需要用户停留确认的关键操作(而非一次性通知)仍应改用会获得焦点的模态对话框。
  • a11y props:命令式 toast(...) 入参(ToastInputmessage / kind / duration)与 ToastHostProps(仅 testID)均不含 a11y 字段;kind(success/error/info)只决定圆点颜色,不带语义角色。
  • 无受控状态(checked / selected / disabled 均不适用)。

现状如实记录:Toast 出现时主动播报 message(SR 可感知),但仍是非阻塞提示(不抢焦点、自动消失);需要用户停留确认的关键操作请用模态对话框。

  • ❌ 不要在 Toast 里放按钮——需要交互的反馈用内联 Confirmation 或模态对话框
  • ❌ 不要堆叠多个 Toast——同一时间最多 1 条,新的把旧的替换
  • ❌ 不要超过 50 字——超过就用模态对话框承载
  • ❌ 不要为「提高可用性」乱挂多个 <ToastHost /> —— 只有最后挂载的那个在收投递;多挂只在 Modal 内有意义(见上方 tip)
  • ❌ 不要依赖 ToastEntry.id 的具体取值 —— 它是内部竞态守卫,不是稳定公共契约