R后悔药 Rue.js
  • 首页
    • 深度指南
    • 实战例子
    • 快速上手
    • 路由指南
    • 术语表
    • 错误代码参考
  • API
    • 合作伙伴
    • 插件
    • 组件库
    • 工具链
    • Text.js
    • 新闻简报
    • 常见问题
    • 团队
    • 版本发布
    • 社区指南
    • 行为规范
    • 隐私政策
  • 赞助
  • 合作伙伴
设置
目录统计
总条目
  • 你好,世界
  • 处理输入
  • Attribute 绑定
  • 条件与循环
  • 表单绑定
  • 简单组件
  • useApp().component 注册
  • Reactive Props Destructure
  • Props 与 useSetup
  • Slots 插槽
  • 原生 Web Components
  • Web Components 分项测试
  • 基础元素与自闭合标签
  • 表达式与插值
  • 属性、className、style 与 Props
  • 对象展开属性(spread props)
  • 条件渲染(?:、&&、null)
  • v-if / r-if 指令
  • v-show / r-show 指令
  • v-pre / r-pre 指令
  • v-once / r-once 指令
  • v-memo / r-memo 指令
  • v-text / r-text 指令
  • v-html / r-html 指令
  • v-on / r-on 指令
  • v-model / r-model 指令
  • 列表渲染与 key
  • v-for / r-for 指令
  • Template 无包装容器
  • Scoped Style 组件作用域样式
  • Fragments(<> … </>)
  • children 插槽与嵌套
  • 组件与 Props 传递
  • 动态组件(Component)
  • Suspense 异步边界
  • 异步组件懒水合
  • 事件处理
  • 受控输入
  • Refs 基础
  • Markdown 编辑器
  • 获取数据
  • 服务端预取
  • 资源
  • 资源(纯 JSX)
  • Rue Islands 总览
  • Island:client:load
  • Island:client:idle
  • Island:client:visible
  • Island:client:media
  • Island:client:interaction
  • Island:client:none
  • Island:client:only
  • Island:安全 Props
  • Island:Manifest Props
  • Island:client:* 编译
  • Context
  • 语言切换(_ 模型)
  • 路由 Demo:总览(嵌套路由)
  • 路由 Demo:守卫(beforeEnter)
  • 路由 Demo:实验页(受守卫保护)
  • 基础计数器
  • shallowRef 浅层 ref
  • triggerRef 手动触发
  • customRef 自定义 ref
  • onActivated 缓存生命周期
  • toRef 响应式句柄
  • toRefs 响应式解构
  • isRef 判定示例
  • isProxy 代理判断
  • isReadonly 判断
  • nextTick 真实业务场景
  • watchPostEffect DOM 读取
  • watchSyncEffect 同步防线
  • onWatcherCleanup 请求清理
  • effectScope 批量停止
  • onScopeDispose 作用域清理
  • getCurrentScope 作用域探针
  • 渲染函数计数器
  • onRenderTracked 调试
  • onRenderTriggered 调试
  • onDeactivated
  • onErrorCaptured 错误捕获
  • useState 计数器
  • 条件渲染
  • map 列表渲染
  • Todo 应用
  • 基础待办事项
  • 本地待办事项
  • 用户资料编辑
  • useState 数组
  • useState 对象
  • 本地 ref 计数器
  • 排序、筛选与网格
  • Store Query Sync 与 URL 状态
  • 树状视图
  • SVG 图表
  • SVG 共享标签命名空间
  • 带过渡动效的模态框
  • Transition mode
  • 子调父方法
  • 父控子计数
  • 组件级 v-model
  • 命名 v-model
  • 组件 emit
  • Hello children
  • 基础 children Box
  • 嵌套 children Box
  • Layout children
  • 过渡动效

Suspense 异步边界

1. 一个边界等待多个异步组件

逻辑:useComponent 默认会把 pending promise 登记到最近的 Suspense;边界收集到任意 pending 后显示 fallback,等全部 resolve 后再恢复 children。

Revenue
¥ 342,800
Q2 转化收入,环比 +12.6%
async chunk
18.4%
转化率
3,214
订单
¥ 106
客单价
Activity

统一边界内的活动流

  1. 销售看板组件完成动态导入并挂载。
  2. 活动流组件与收入组件共享同一个 Suspense fallback。
  3. 加载完成后,边界重新渲染 children 内容。

2. 内层边界交给父 Suspense

左边是默认嵌套:外层绿色框还在,只显示黄色的内层 fallback。右边开启 suspensible:内层 pending 被继续登记到父边界,绿色外层框会整块被父级 fallback 替换。

默认:内层自己处理

外层框保持可见
默认嵌套边界
这块绿色区域代表外层 children。是否被替换,是两个场景最明显的区别。
外层 children 仍在
Activity

默认异步内容已解析

  1. 销售看板组件完成动态导入并挂载。
  2. 活动流组件与收入组件共享同一个 Suspense fallback。
  3. 加载完成后,边界重新渲染 children 内容。

开启:交给父级接管

外层框会被替换
开启 suspensible 的嵌套边界
这块绿色区域代表外层 children。是否被替换,是两个场景最明显的区别。
resolved 后才出现
Activity

交给父边界的活动流

  1. 销售看板组件完成动态导入并挂载。
  2. 活动流组件与收入组件共享同一个 Suspense fallback。
  3. 加载完成后,边界重新渲染 children 内容。

3. 退出 Suspense 控制

逻辑:这是组件级开关。useComponent 设置 suspensible: false 后不会向最近边界登记 pending,因此外层 fallback 不会显示,组件自己的 loading 负责占位。

Activity

本地 loading 控制的活动流

  1. 销售看板组件完成动态导入并挂载。
  2. 活动流组件与收入组件共享同一个 Suspense fallback。
  3. 加载完成后,边界重新渲染 children 内容。
提示:useComponent 的 loader 会按函数引用缓存。刷新页面能重新看到首屏 fallback;同一页面内再次渲染已加载组件会直接命中缓存。
import { Suspense, type FC, useComponent } from '@rue-js/rue';

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

const resolveAfter = <P,>(component: FC<P>, ms: number) =>
  wait(ms).then(() => ({ default: component }));

const RevenuePanel: FC<{ period?: string }> = props => (
  <article className="rounded-box border border-primary/25 bg-primary/10 p-4">
    <div className="text-xs uppercase tracking-[0.22em] opacity-60">Revenue</div>
    <div className="mt-2 text-3xl font-semibold">¥ 342,800</div>
    <div className="mt-1 text-sm opacity-75">
      {props.period ?? '本周'} 转化收入,环比 +12.6%
    </div>
  </article>
);

const ActivityPanel: FC<{ title?: string }> = props => (
  <section className="rounded-box border border-accent/25 bg-accent/10 p-4">
    <div className="text-xs uppercase tracking-[0.22em] opacity-60">Activity</div>
    <h3 className="mt-2 text-xl font-semibold">{props.title ?? '异步活动流'}</h3>
    <ol className="mt-4 space-y-2 text-sm">
      <li>收入组件和活动流组件共享同一个 Suspense fallback。</li>
      <li>加载完成后,边界重新渲染 children 内容。</li>
    </ol>
  </section>
);

const AsyncRevenuePanel = useComponent(() =>
  resolveAfter(RevenuePanel, 900),
);

const AsyncActivityPanel = useComponent(() =>
  resolveAfter(ActivityPanel, 1400),
);

const AsyncNestedDefaultActivityPanel = useComponent(() =>
  resolveAfter(ActivityPanel, 2600),
);

const AsyncNestedSuspensibleActivityPanel = useComponent(() =>
  resolveAfter(ActivityPanel, 2800),
);

const AsyncLocalActivityPanel = useComponent(
  () => resolveAfter(ActivityPanel, 3200),
  {
    loading: () => <div>本地 loading</div>,
    suspensible: false,
  },
);

const NestedOuterFrame: FC<{ title: string; children?: unknown }> = props => (
  <div className="rounded-box border-2 border-success/45 bg-success/10 p-4">
    <strong>{props.title}</strong>
    <div>绿色外层区域是否还在,是两个嵌套场景最明显的区别。</div>
    <div className="mt-3">{props.children}</div>
  </div>
);

export default function Demo() {
  return (
    <>
      <Suspense fallback={<div>统一 fallback 正在加载</div>}>
        <AsyncRevenuePanel period="Q2" />
        <AsyncActivityPanel title="统一边界内的活动流" />
      </Suspense>

      <div className="grid gap-4 md:grid-cols-2">
        <Suspense fallback={<div>默认父级 fallback(不会显示)</div>}>
          <NestedOuterFrame title="默认:外层绿色框仍然可见">
            <Suspense fallback={<div>默认:内层 fallback 正在加载</div>}>
              <AsyncNestedDefaultActivityPanel title="默认异步内容已解析" />
            </Suspense>
          </NestedOuterFrame>
        </Suspense>

        <Suspense fallback={<div>父级 fallback 接管整块外层内容</div>}>
          <NestedOuterFrame title="开启 suspensible:加载时这块会被替换">
            <Suspense suspensible fallback={<div>内层 fallback 会被父级覆盖</div>}>
              <AsyncNestedSuspensibleActivityPanel title="交给父边界的活动流" />
            </Suspense>
          </NestedOuterFrame>
        </Suspense>
      </div>

      <Suspense fallback={<div>不会显示的外层 fallback</div>}>
        <AsyncLocalActivityPanel title="本地 loading 控制的活动流" />
      </Suspense>
    </>
  );
}
页内导航
文档
  • 深度指南
  • 实战例子
  • 快速上手
  • 术语表
  • 错误码参照表
关于
  • 常见问题
  • 团队
  • 版本发布
  • 社区指南
生态
  • 插件
  • 组件库
  • 路由指南
  • Text.js
© 2024-2026 Xiangmin Liu
Rue.JS