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
  • 过渡动效

动态组件(Component)

1. 直接切换原生标签和组件定义

这里显式导入并使用 <Component>,is 可以在字符串标签名和函数组件之间来回切换。

children 已透传
当前 is: article
验证点:native element、直接组件定义、children 透传。
说明:切到 SalesCard 时,tone 也会跟着透传给目标组件。

2. 运行时注册字符串组件名

下面这个小区域直接在当前页面里使用导出的 <Component>,并通过 useApp().component('RegisteredMetric', RegisteredMetric) 把字符串名注册到当前 runtime。

运行时注册
CardView
通过 useApp().component('RegisteredMetric', RegisteredMetric) 注册后,字符串名会被解析成组件定义
children 一样会透传
这里不再额外挂一个子应用,只把字符串组件名注册到当前 runtime,再直接渲染 <Component>。
因为已经注册了字符串名,所以 Component 会先查注册表,再决定最终渲染哪个组件。
这也顺带验证了字符串名路径下的 children 透传。
注意:如果 is 最终解析成原生 input、select、textarea 之类的节点,写在 <Component> 上的 v-model / r-model 不能像静态原生标签那样在编译期展开,应该在最终原生节点处处理。
import {
  Component,
  type FC,
  ref,
  useApp,
} from '@rue-js/rue';

const SalesCard: FC<{ title: string; value: string; detail: 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">{props.title}</div>
    <div className="mt-2 text-3xl font-semibold">{props.value}</div>
    <div className="mt-1 text-sm opacity-75">{props.detail}</div>
    <div className="mt-4">{props.children}</div>
  </article>
);

const StatusStrip: FC<{ title: string; detail: string }> = (props) => (
  <section className="rounded-box border border-info/25 bg-info/10 p-4">
    <div className="font-semibold">{props.title}</div>
    <div className="text-sm opacity-75">{props.detail}</div>
    <div className="mt-4">{props.children}</div>
  </section>
);

const RegisteredMetric: FC<{ title: string; value?: string; detail: string }> = (props) => (
  <article className="rounded-box border border-secondary/25 bg-secondary/10 p-4">
    <div className="text-xs uppercase tracking-[0.22em] opacity-60">{props.title}</div>
    <div className="mt-2 text-2xl font-semibold">{props.value ?? 'Registered'}</div>
    <div className="mt-1 text-sm opacity-75">{props.detail}</div>
    <div className="mt-4">{props.children}</div>
  </article>
);

const RegisteredNotice: FC<{ title: string; detail: string }> = (props) => (
  <section className="rounded-box border border-warning/35 bg-warning/15 p-4">
    <div className="font-semibold">{props.title}</div>
    <div className="mt-1 text-sm opacity-75">{props.detail}</div>
    <div className="mt-4">{props.children}</div>
  </section>
);

const Demo: FC = () => {
  const shell = ref<'native' | 'card' | 'notice'>('native');
  const registryMode = ref<'metric' | 'notice'>('metric');

  useApp(() => null)
    .component('RegisteredMetric', RegisteredMetric as FC<any>)
    .component('RegisteredNotice', RegisteredNotice as FC<any>);

  const resolveType = () =>
    shell.value === 'native'
      ? 'article'
      : shell.value === 'card'
        ? SalesCard
        : StatusStrip;

  const resolveProps = () =>
    shell.value === 'native'
      ? {
          className: 'rounded-box border border-base-300 bg-base-100 p-4 shadow-sm',
        }
      : shell.value === 'card'
        ? {
            title: '今日成交额',
            value: '¥ 128,400',
            detail: '比昨日 +8.2%',
          }
        : {
            title: '状态切换',
            detail: '当前目标由 is 决定',
          };

  return (
    <div className="grid gap-6">
      <Component is={resolveType()} {...resolveProps()}>
        <span className="badge badge-outline badge-sm">children 已透传</span>
      </Component>

      <Component
        is={registryMode.value === 'metric' ? 'RegisteredMetric' : 'RegisteredNotice'}
        title="运行时注册"
        value="CardView"
        detail="通过字符串名解析到已注册组件"
      >
        <span className="badge badge-outline badge-sm">children 一样会透传</span>
      </Component>
    </div>
  );
};

export default Demo;
页内导航
文档
  • 深度指南
  • 实战例子
  • 快速上手
  • 术语表
  • 错误码参照表
关于
  • 常见问题
  • 团队
  • 版本发布
  • 社区指南
生态
  • 插件
  • 组件库
  • 路由指南
  • Text.js
© 2024-2026 Xiangmin Liu
Rue.JS