一、核心步骤
从 Vue Lib 到 Weex 原生 UIKit 元素的完整流程。
首先,用户的需求是 “列举 Weex 做的事情” 并 “结合最新源码标位置”,所以思考过程要先拆解流程:从 Vue 编译产物处理、JS-Native 通信、Virtual DOM 解析、DOM 构建与布局计算、渲染指令生成、原生 View 创建与更新,这几个核心步骤。每个步骤都要对应 Weex 的具体工作,还要找到最新源码中的对应类和方法。
第一步,应该是 Vue 编译产物的适配。因为 Vue 写的代码要被 Weex 识别,Weex 需要处理 Vue 的模板编译结果(比如 render 函数生成的 Virtual DOM),还要注入 Weex 的 JS Runtime API。这里要找最新源码中 JS Runtime 的初始化类,比如 WXJSContext 或 WXBridgeCore,因为 JS 环境的搭建是第一步。
第二步,JS-Native 通信的建立。Vue 的 Virtual DOM 变化需要传递给 Native,所以 Weex 的 Bridge 核心要注册通信方法(比如 callNative),这里对应 WXBridgeCore 的 invokeNative 方法,之前讨论过 bridge 队列,这里要结合最新源码中通信任务的调度。
第三步,Virtual DOM 的解析与 DOM 树构建。Weex 需要把 Vue 的 Virtual DOM(JSON 结构)解析成 Native 侧的 DOM 节点(WXDOMNode),这部分现在在 WXDOMCore 中,比如 createNode 方法,处理节点创建、属性更新,还要维护 DOM 树结构。
第四步,样式解析与 Yoga 布局计算。Vue 的样式(比如 flex)要被 Weex 解析,转换为 Yoga 的布局属性,然后在 dom 线程计算布局,这部分在 WXDOMCore 的_scheduleLayout 方法,调用 Yoga 的 calculateLayout。
第五步,渲染指令生成与传递。DOM 和布局计算完成后,要把结果封装成渲染指令(节点 ID、frame、类型等),通过主线程传递给渲染核心,这里是 WXDOMCore 的_notifyRender 方法,切换到 main queue。
第六步,原生 UIKit 元素的创建与更新。渲染核心(WXRenderCore)接收指令,创建对应 UIKit 控件(UILabel、UIImageView 等),或复用已有 View,更新 frame 和样式,这部分在 WXRenderCore 的 render 方法,还有具体的 View 工厂类(比如 WXViewFactory)。
第七步,事件绑定与反向通信。原生 UI 的事件(比如点击)要传递回 Vue,Weex 需要注册 Native 事件,通过 Bridge 回调 JS,这里对应 WXEventDispatcher 和 WXBridgeCore 的 callbackJS 方法。
具体的逻辑不做深入探讨,可以查看源码研究。
二、Yoga
1. 支持"增量布局更新"
当组件样式变化时,Yoga 仅重新计算受影响的组件树分支,而非全量重算,大幅减少 RN 应用的布局耗时和卡顿』Yoga 是如何实现仅计算受影响的组件树分支的?类似有个打标记,标记为 dirty?
Yoga 实现增量布局的核心就是 「Dirty 标记机制」+「组件树依赖传播」—— 通过标记 “受影响的节点”,并仅处理这些节点及其关联分支,避免全量重算。
1. YogaNode 与 Dirty 状态标识
Yoga 中每个组件对应一个 YogaNode(布局计算的最小单元),每个节点都包含 3 个关键状态标记(用于判断是否需要重算):
dirtyFlags(核心标记):记录节点的 “脏状态类型”,主要分两类:
LAYOUT_DIRTY:节点自身样式(如 width、flex)或子节点布局变化,需要重新计算自身布局;
MEASURE_DIRTY:节点的测量相关属性(如 measureFunction 自定义测量逻辑)变化,需要先重新测量尺寸,再计算布局。
isLayoutClean:布尔值,快速判断节点是否 “干净”(无脏状态),避免重复检查 dirtyFlags;
childCount + children 指针:维护子节点列表,用于后续遍历依赖分支。
2. 脏状态触发与传播:从 “变化节点” 到 “根节点” 的冒泡
当组件样式变化时(如 RN 中修改 style={{ flex: 2 }}),Yoga 会触发以下流程:
步骤 1:标记自身为 Dirty
直接修改变化节点的 dirtyFlags |= LAYOUT_DIRTY(或 MEASURE_DIRTY),同时设置 isLayoutClean = false。步骤 2:向上冒泡通知父节点
由于父节点的布局(如尺寸、位置)依赖子节点的布局结果(比如父节点是 flex:1,子节点尺寸变化会影响父节点的剩余空间分配),因此会递归向上遍历父节点,直到根节点,将所有 “依赖节点” 都标记为 LAYOUT_DIRTY。
关键优化:父节点仅标记 “需要重算”,但不会立即计算,避免中途重复触发计算。步骤 3:跳过已标记的节点
若某个节点已被标记为 Dirty,后续重复触发时会直接跳过(避免重复冒泡),提升效率。
3. 布局计算阶段:只处理 Dirty 分支,跳过干净节点(DFS)
当 Yoga 触发布局计算(如 RN 渲染帧触发、组件挂载完成)时,会从根节点开始遍历组件树,但仅处理 “Dirty 节点及其子树”:
步骤 1:根节点判断状态
若根节点是干净的(isLayoutClean = true),直接终止计算(全量跳过);若为 Dirty,进入分支处理。步骤 2:递归处理 Dirty 分支
对每个节点,先检查自身状态:若干净:直接复用上次缓存的布局结果(x/y/width/height),不重算;
若 Dirty:
先处理子节点:如果子节点是 Dirty,先递归计算子节点布局(保证父节点计算时依赖的子节点数据是最新的);
再计算自身布局:根据 Flex 规则(如 flexDirection、justifyContent)和子节点布局结果,计算自身的最终尺寸和位置;
清除 Dirty 标记:计算完成后,设置 dirtyFlags = 0、isLayoutClean = true,标记为干净。
步骤 3:增量更新的核心效果
比如修改一个列表项的 margin,只会标记该列表项 → 父列表容器 → 根节点为 Dirty,其他列表项、页面其他组件均为干净,会直接跳过计算,仅重算 “列表项→父容器” 这一小分支。
2. Flex 布局逻辑如何到 Native 系统
Flex 布局逻辑,或者说 DSL,是如何翻译为 iOS 的 AutoLayout 和 Android 的 LayoutParams 的?
Yoga 先将 Flex DSL 解析为统一的「布局计算结果」(节点的 x/y/width/height、间距、对齐方式等),再根据平台差异,将计算结果 “映射” 为对应平台的原生布局规则——iOS 映射为 AutoLayout 约束,Android 映射为 LayoutParams + 原生布局容器属性。
1. 第一步:通用前置流程(跨平台统一)
无论 iOS 还是 Android,Yoga 都会先完成以下步骤,屏蔽 Flex DSL 的解析差异:
解析 Flex 样式:将上层框架的 Flex 配置(如 RN 的 StyleSheet、Weex 的模板样式)解析为 YogaNode 的属性(如 flexDirection、justifyContent、margin、padding 等);
执行布局计算:通过 Flexbox 算法(基于 Web 标准),计算出每个 YogaNode 的最终布局数据:
固定属性:width/height(含 auto/flex 计算后的具体数值)、x/y(相对父节点的坐标);
间距属性:marginLeft/Top/Right/Bottom、paddingLeft/Top/Right/Bottom;
对齐属性:alignItems、justifyContent 对应的节点相对位置关系;
输出标准化布局数据:将上述结果封装为平台无关的结构体,供后续平台映射使用。
2. 第二步:iOS 端:映射为 AutoLayout 约束(NSLayoutConstraint)
AutoLayout 的核心是「基于约束的关系描述」(而非直接设置坐标),因此 Yoga 会将 “计算出的具体尺寸 / 位置” 转化为 UIView 的约束(NSLayoutConstraint),核心映射规则如下:一一翻译 css 规则到 iOS AutoLayout 写法:
| Flex 核心属性 | 对应的 AutoLayout 约束逻辑 |
|---|---|
width: 100 | 映射为 view.widthAnchor.constraint(equalToConstant: 100) |
height: auto | 先通过 Yoga 计算出具体高度(如文字高度、子节点包裹高度),再映射为 heightAnchor 约束;若为 flex:1,则映射为 heightAnchor.constraint(equalTo: superview.heightAnchor, multiplier: 1)(占满父容器剩余高度) |
marginLeft: 20 | 映射为 view.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 20) |
marginTop: 15 | 映射为 view.topAnchor.constraint(equalTo: superview.topAnchor, constant: 15) |
justifyContent: center(父节点 flexDirection: row) | 父节点约束:view.centerXAnchor.constraint(equalTo: superview.centerXAnchor);若有多个子节点,通过调整子节点间的 spacing 约束实现均匀分布 |
alignItems: center(父节点 flexDirection: column) | 子节点约束:view.centerYAnchor.constraint(equalTo: superview.centerYAnchor) |
flex: 1(子节点) | 映射为 view.widthAnchor.constraint(equalTo: superview.widthAnchor, multiplier: 1)(横向占满)+ 父节点的 distribution 约束(分配剩余空间) |
补充信息:
Yoga 会为每个
UIView关联一个YogaNode,布局计算完成后,通过YogaKit(或上层框架如 RN 的原生层)自动生成约束;支持 “约束优先级” 适配:比如
flex:1对应的约束优先级会高于固定尺寸约束,确保 Flex 规则优先生效;混合布局兼容:若原生视图已有部分 AutoLayout 约束,Yoga 会生成 “补充约束”,避免冲突(通过
active属性控制约束启用 / 禁用)。
共同学习,写下你的评论
评论加载中...
作者其他优质文章