VirtualDom 内核
这一部分讲 dioxus-core,整个框架里和平台无关的那一层。源码在 packages/core/src(约 1 万行),这一部分要读的是 virtual_dom.rs、runtime.rs、scope_arena.rs、scope_context.rs、scopes.rs、diff/{node,component,iterator}.rs、arena.rs、mutations.rs、scheduler.rs、tasks.rs、events.rs、suspense/、error_boundary.rs。浏览器、服务端、桌面都只是给它喂事件、收指令:VirtualDom 不知道 DOM 长什么样,它只产出一串 WriteMutations 调用,由渲染器去执行。
02 rsx 与组件讲到 rsx! 产出 VNode 为止;这一部分从「组件函数被谁调用」讲起,到「改 DOM 的指令怎么发出去」为止。更新主线中间的那一大段(mark_dirty → render_immediate → run_scope → diff → WriteMutations)都在这里。
- VirtualDom 与 Runtime:
VirtualDom拿着组件树(scopes)、脏队列和调度通道的接收端,Runtime拿着其余所有共享状态,Rc共享、用线程局部的栈表示「当前 runtime」;rebuild、wait_for_work、render_immediate、wait_for_suspense几个入口怎么被 web 主循环和 SSR 组合起来。 - Scope 与 hooks:每个组件实例一个
ScopeId,用户组件从 3 号开始;run_scope在组件自己的ReactiveContext里调用组件函数;use_hook只按调用序号存取,hook 放进条件里时类型不同就 panic、类型相同就静默串值;context 沿父链查找;卸载时先子后父,hook 倒序 drop。 - diff:同一个模板只比动态节点和动态属性,静态部分不参与比较;模板不同就整块替换;文本、属性、组件、Fragment、占位符各自怎么比;组件靠
memoize决定重不重跑;ElementId从 slab 里分配,删掉的 id 马上被下一个节点复用。 - 带 key 的列表:不带 key 时按位置配对,中间插一项会让后面每一项都被改写,组件状态跟着位置走、错位;带 key 时先剥两端,中间用最长递增子序列找出不动的节点,只移动其余的。
- Mutations:
WriteMutations的每个方法,指令是一台栈机器,模板整块克隆再按路径填动态部分;Mutations和NoOpMutations。 - 调度:scope、任务与 effect:脏 scope 和被唤醒的任务按高度从根往下交错执行,effect 等它们都做完才跑;
SchedulerMsg;同一轮写多次 signal 只重跑一次。 - 事件:
Runtime::handle_event由ElementId找到挂载点,沿模板和父挂载点冒泡找监听器;stop_propagation、prevent_default。 - Suspense 与错误边界:组件
?出RenderError::Suspended或错误之后,最近的边界怎么接住;SSR 里怎么用这两个。
flowchart TB rsx["rsx 产出 VNode"] --> runscope["run_scope 跑组件"] runscope --> scope["Scope 与 hooks"] runscope --> diffnode["diff 新旧 VNode"] diffnode --> keyed["带 key 的列表"] diffnode --> muts["WriteMutations 指令"] muts --> renderer["渲染器改 DOM"] renderer --> evt["事件 handle_event"] evt --> sched["调度 脏 scope 队列"] sched --> runscope runscope --> susp["Suspense 与错误边界"]
前四页(VirtualDom 本身、scope、diff、带 key 的列表)讲「一次重渲染里发生什么」,后四页讲「重渲染由谁触发、结果怎么送出去、挂起和出错时怎么办」。signal 怎么记录订阅、写入时怎么通知组件,在 04 响应式;WriteMutations 在浏览器里的实现在 WebsysDom 与 interpreter,在服务端则不用指令,dioxus-ssr 直接沿 VNode 树写 HTML。
💬 关联讨论
发起讨论加载中...