渲染内建

本文档收敛 Dujie 中直接参与最终渲染结果的语言内建:

  • main
  • element
  • text
  • fragment
  • comment

它们不按普通函数库看待,而是渲染系统和宿主平台之间的语言级边界。

widget

widget 是抽象渲染结果类型。

它不是某一种具体宿主节点类型,而是“可被渲染系统接受的 UI 树值”。

当前规则:

  • widget 是一等类型
  • 可以赋值、传参、返回
  • 可以放入 list<widget>
  • 可以作为 struct 字段或 opt<widget> 的内部类型
  • 不支持比较
  • 不能作为 map 的 key

组件函数

组件函数的判定规则:

  • 只有显式返回类型恰好为裸 widget 的函数才算组件函数
  • list<widget>opt<widget>map<string, widget> 等都不算组件函数返回类型
  • 判定只看显式签名,不靠函数体推导

组件函数本身仍然是普通 Dujie 函数,只是返回 widget

保留内建名

第一阶段将以下名字视为保留内建名:

  • main
  • element
  • text
  • fragment
  • comment

用户不能定义同名顶层函数覆盖它们。

main

main 是入口 intrinsic,不等同于普通导出函数。

规则:

  • main 的返回类型必须显式写为裸 widget
  • main 的参数写法按普通 Dujie 函数规则处理
  • main 不能是泛型函数

也就是说,这些都可以是合法的语言层声明:

func main() -> widget
func main(title: string, count: opt<int>) -> widget
func main({ title: string, count: int = 0 }) -> widget

入口适配层

语言层允许用户自由书写 main 参数形状,但宿主或代码生成层需要提供与之匹配的入口适配。

当前约定是:

  • 如果 main 使用位置参数,Rust 侧按顺序暴露对应参数
  • 如果 main 使用具名参数,Rust 侧可以生成对应的参数结构体来承接输入
  • opt<T> 在 Rust 侧映射为对应的 Option<T>
  • 默认值由生成的适配层补齐,不要求 Rust 调用方手动补默认值

例如,类似下面的 Dujie 入口:

func main(title: string, count: opt<int>) -> widget

可以在 Rust 侧暴露为“顺序参数”风格入口;而:

func main({ title: string, count: int = 0 }) -> widget

可以在 Rust 侧暴露为“参数结构体”风格入口。

动态边界示例

如果用户本身就把 main 写成动态边界形式:

func main(props: map<string, any>) -> widget

则:

  • props["k"] 缺 key 时 panic
  • any 中取具体值时,使用 is 做显式收窄

text

签名:

text(value: string) -> widget

职责:

  • 构造文本节点
  • 不承担格式化职责

fragment

签名:

fragment(children: list<widget>) -> widget

职责:

  • 聚合多个子节点
  • 不承诺生成真实宿主节点

规则:

  • children 可以为空
  • 嵌套 fragment 在渲染语义上可展平
  • fragment([]) 表示不产生可见子节点

comment

签名:

comment(value: string) -> widget

职责:

  • 构造注释或调试节点

规则:

  • 不影响布局
  • 不影响命中测试
  • 不影响事件
  • 不影响可访问性结构
  • 后端可以选择保留或擦除

element

第一阶段按具名参数函数理解:

func element({
    name: string,
    props: map<string, any> = {},
    children: list<widget> = [],
}) -> widget

规则:

  • name 必填
  • props 默认空 map
  • children 默认空 list
  • props 是宿主边界属性字典
  • children 是显式子节点列表

第一阶段不引入联合类型,因此 element.props 保持:

map<string, any>

这代表宿主边界的开放属性集合,不代表普通语言内部推荐的静态数据模型。

渲染树语义

普通组件函数最终会展开成由 intrinsic 构成的渲染树。

语言层关心的是渲染语义,而不是中间组件调用层级。

子节点顺序

  • children 顺序有语义
  • 子节点不能重排

fragment 展平

如果某个子节点是 fragment(...),渲染时可以将其展平成内部子节点序列。

也就是说,这两者在渲染层可以视为等价:

fragment(children: [a, b])
fragment(children: [fragment(children: [a]), b])

当前不定义

第一阶段不定义:

  • widget 的比较或相等性
  • diff 语义
  • 渲染后端的具体 Android / iOS / Web 映射细节
  • 响应式更新机制