1. Taro适配各端整体流程
开始了解各个细节之前,让我们先整体过一遍taro适配各端的流程(接下来将通过微信小程序、h5、React Native三端举例)
我们可简单将taro适配各端的过程分为 编译时 和 运行时:
- 编译时:将JSX转为平台代码,输出可部署的静态文件
- 运行时:用户打开应用后如何处理用户交互、更新界面等等
1.1 编译时
整个编译时过程可概括为:
- 使用 Babel 解析 JSX 代码为抽象语法树(AST)
- 将 AST 转换为不同平台的模板语法
- 微信小程序 → WXML
- H5 → HTML
- React Native → React Native 组件
- PostCSS 处理样式单位转换 (px → rpx/rem)
1.1.1 将JSX代码解析为AST (Parsing)
在编译时,Taro使用 @babel/parser 将JSX代码(Taro代码)解析为抽象语法树(AST)
Taro原代码:
function App() {
return (
<View className="container">
<Text>Hello Taro</Text>
</View>
);
}
使用 @babel/parser 将JSX代码(Taro代码)解析为抽象语法树(AST):
{
"type": "JSXElement",
"openingElement": {
"name": { "name": "View" },
"attributes": [
{ "name": "className", "value": "container" }
]
},
"children": [
{
"type": "JSXElement",
"openingElement": { "name": { "name": "Text" } },
"children": [{ "type": "JSXText", "value": "Hello Taro" }]
}
]
}
1.1.2 将AST转化为目标平台代码(Transformation)
通过 @babel/traverse 遍历AST,将其转化为目标平台代码
组件名映射:
// React 组件 → 平台组件
{
'View': {
'weapp': 'view',
'h5': 'div',
'rn': 'View'
},
'Text': {
'weapp': 'text',
'h5': 'span',
'rn': 'Text'
}
}
属性转化:
// className → class (小程序)
// className → className (RN)
// style 对象 → style 字符串 (小程序)
{ color: 'red', fontSize: 14 } → "color: red; font-size: 14px"
事件映射:
{
'onClick': {
'weapp': 'bindtap',
'alipay': 'onTap',
'h5': 'onclick',
'rn': 'onPress'
},
'onChange': {
'weapp': 'bindinput',
'h5': 'onchange',
'rn': 'onChangeText'
}
}
根据平台生产目标代码:
微信小程序 (WXML):
<view class="container">
<text>Hello Taro</text>
</view>
H5 (HTML):
<div class="container">
<span>Hello Taro</span>
</div>
React Native (JSX):
<View className="container">
<Text>Hello Taro</Text>
</View>
1.2 运行时
当用户与界面交互时,会调用平台api、更新界面等等,taro是怎么做的呢,大致可分为以下流程:
- 创建平台无关的虚拟 DOM 树,使用diff算法比较新旧虚拟 DOM 树,计算最小更新集合
- 使用适配器模式统一不同平台的 API。
- 协调器负责将虚拟 DOM 的变化应用到真实平台。
1.2.1 虚拟 DOM (Virtual DOM)
Taro 使用虚拟 DOM 作为中间层,实现平台无关的 UI 描述。
虚拟节点结构:
class VNode {
constructor(type, props, children) {
this.type = type; // 节点类型
this.props = props; // 属性
this.children = children; // 子节点
}
}
创建虚拟DOM:
const vnode = createElement(
'View',
{ className: 'container' },
createElement('Text', {}, 'Hello'));
// 结果:
// {
// type: 'View',
// props: { className: 'container' },
// children: [
// { type: 'Text', props: {}, children: ['Hello'] }
// ]
// }
1.2.2 Diff算法
比较新旧虚拟 DOM 树,计算最小更新集合。
diff过程:
function diff(oldVNode, newVNode) {
// 1. 节点类型变化 → REPLACE
if (oldVNode.type !== newVNode.type) {
return [{ type: 'REPLACE', oldVNode, newVNode }];
}
// 2. 属性变化 → UPDATE_PROPS
const propPatches = diffProps(oldVNode.props, newVNode.props);
// 3. 子节点变化 → UPDATE_CHILDREN
const childPatches = diffChildren(oldVNode.children, newVNode.children);
return [...propPatches, ...childPatches];
}
diff示例:
// 旧节点
<View className="box">
<Text>Old</Text>
</View>
// 新节点
<View className="box updated">
<Text>New</Text>
<Button>Click</Button>
</View>
// Diff 结果
[
{ type: 'PROPS', patches: [{ key: 'className', value: 'box updated' }] },
{ type: 'CHILDREN', patches: [
{ index: 0, patches: [{ type: 'TEXT', value: 'New' }] },
{ index: 1, patches: [{ type: 'CREATE', vnode: Button }] }
]}
]
1.2.3 平台适配器 (Platform Adapter)
使用适配器模式统一不同平台的 API。
适配器接口:
class PlatformAdapter {
createElement(type) {}
createTextNode(text) {}
setAttribute(element, key, value) {}
appendChild(parent, child) {}
// ... 其他 DOM 操作
}
微信小程序适配器:
class WeappAdapter extends PlatformAdapter {
createElement(type) {
// 创建小程序虚拟节点
return new WeappElement(type);
}
render(element) {
// 生成 WXML 模板
return element.toTemplate();
}
}
H5适配器:
class H5Adapter extends PlatformAdapter {
createElement(type) {
// 创建真实 DOM 节点
return document.createElement(type);
}
render(element) {
// 返回 HTML
return element.outerHTML;
}
}
1.2.4 协调器 (Reconciler)
协调器负责将虚拟 DOM 的变化应用到真实平台。
工作流程:
class Reconciler {
mount(vnode, container) {
// 1. 创建真实节点
const node = this.createNode(vnode);
// 2. 挂载到容器
this.platformAdapter.appendChild(container, node);
}
update(newVNode) {
// 1. Diff 计算补丁
const patches = diff(this.currentVNode, newVNode);
// 2. 应用补丁
this.applyPatches(patches);
// 3. 更新当前树
this.currentVNode = newVNode;
}
}
1.3 整体流程概述
2. 定位源码,细节拆解
了解了taro整个工作流程我们也许会产生几个问题:
- babel将jsx代码转化为了ast,taro 是怎么遍历 ast 并将其转化为目标平台代码的
- taro是怎么实现样式转换的
- taro是怎么创建虚拟dom树,diff算法的细节
- taro平台适配器的细节
- taro是怎么适配不同平台的api,并且更新界面的
接下来我们逐一解答,并标注各自在源码中的实现位置:
2.1 babel将jsx代码转化为了ast,taro 是怎么遍历 ast 并将其转化为目标平台代码的
Taro代码转为AST是通过babel预设实现的,本质上是babel做的工作,但是将ast转为目标平台代码是Taro实现的,大致流程为:
babel生成AST --> Taro识别要生成的平台代码,遍历AST并更改AST --> 通过babel生成目标平台代码
Taro 的 AST 转换主要在以下源码包中实现:
packages/taro-transformer-wx/
├── src/
│ ├── index.ts # 主入口,定义 Babel 遍历规则
│ ├── render.ts # JSX 渲染逻辑,处理条件、循环等
│ ├── jsx.ts # JSX 元素解析和转换
│ ├── class.ts # 类组件处理
│ └── utils.ts # 工具函数
完整转化流程:
JSX元素转换:
条件渲染转化流程:
循环渲染转换流程:
2.2 taro是怎么实现样式转换的
Taro 的样式转换主要通过 PostCSS 插件实现,源码位置:
packages/
├── postcss-pxtransform/ # 核心单位转换插件
│ ├── index.js # 主入口 (1-372行)
│ └── lib/
│ └── pixel-unit-regex.js # 像素单位正则匹配
│
├── taro-webpack5-runner/src/postcss/
│ ├── postcss.mini.ts # 小程序 PostCSS 配置 (7-99行)
│ ├── postcss.h5.ts # H5 PostCSS 配置 (7-106行)
│ └── postcss.harmony.ts # HarmonyOS PostCSS 配置 (7-100行)
│
├── taro-rn-style-transformer/ # React Native 样式转换
│ └── src/
│ ├── transforms/
│ │ ├── index.ts # 样式转换入口 (156-229行)
│ │ └── postcss.ts # PostCSS 插件配置 (1-113行)
│ └── config/
│ └── rn-stylelint.json # RN 样式校验规则
│
└── taroize/src/
└── wxml.ts # WXML 样式单位转换 (179-227行)
Taro使用多个 PostCSS 插件协同工作:
postcss-import // 处理 @import 语句
↓
autoprefixer // 添加浏览器前缀
↓
postcss-pxtransform // 单位转换 (核心)
↓
postcss-html-transform // HTML 标签转换
↓
postcss-url // 处理 url()
完整转换流程:
css单位转换流程:
平台转换规则对比:
内联样式转换流程:
2.3 taro是怎么创建虚拟dom树、diff算法的细节
Taro通过React的diff算法(react-reconciler) 实现新旧DOM树对比,但是创建虚拟DOM节点以及将 React 的更新转换为平台操作都是Taro实现的。
虚拟 DOM → Reconciler → 平台适配器 → 平台特定代码
Taro 虚拟DOM实现源代码位置:
packages/taro-runtime/src/
├── dom/
│ ├── node.ts (1-341行) # TaroNode 基类 ⭐
│ ├── element.ts # TaroElement 元素节点
│ ├── document.ts # TaroDocument 文档对象
│ ├── tree.ts # DOM 树操作
│ └── event-target.ts # 事件目标基类
│
├── hydrate.ts # 序列化虚拟 DOM
└── utils/index.ts # 工具函数packages/taro-react/src/
├── reconciler.ts (1-500行) # React Reconciler 集成 ⭐
├── render.ts # 渲染函数
└── props.ts # 属性处理
虚拟DOM创建过程:
Diff算法详细过程:
属性diff过程:
子节点diff过程:
补丁应用流程:
2.4 taro平台适配器的细节
React 组件 → Taro Runtime → 平台适配层 → 平台特定代码
Taro 的平台适配器源码实现:
packages/taro-runtime/src/
├── dsl/
│ ├── common.ts (91-415行) # 页面配置创建 ⭐
│ ├── instance.ts # 实例管理
│ └── hooks.ts # 生命周期钩子
│
├── dom/
│ ├── root.ts # 根元素 (平台渲染入口)
│ ├── node.ts # 节点基类
│ └── element.ts # 元素节点
│
├── bom/
│ ├── document.ts # 文档对象适配
│ └── window.ts # 窗口对象适配
│
└── index.ts # 导出接口packages/taro-platform-*/ # 各平台特定实现
├── taro-platform-weapp/ # 微信小程序
├── taro-platform-h5/ # H5
├── taro-platform-harmony/ # HarmonyOS
└── taro-platform-rn/ # React Native
平台适配器整体框架:
页面生命周期适配:
更新流程:
数据流转:
2.5 taro是怎么适配不同平台的api,并且更新界面的
核心 API 适配和更新文件源码位置:
packages/taro-runtime/src/
├── dom/
│ ├── root.ts (83-192行) # TaroRootElement 更新队列 ⭐
│ ├── node.ts (329-331行) # enqueueUpdate 入队更新 ⭐
│ ├── element.ts (205-278行) # 元素属性更新
│ └── style.ts (17-154行) # 样式更新
│
├── dsl/
│ ├── common.ts (91-415行) # createPageConfig 页面配置
│ └── next-tick.ts # nextTick 实现
│
└── interface/
└── hydrate.ts (6行) # setData 接口定义packages/taro-api/src/
├── tools.ts # API 工具函数
└── interceptor/ # API 拦截器packages/taro-h5/src/api/ # H5 API 实现
packages/taro-platform-weapp/ # 微信小程序 API 实现
packages/taro-platform-harmony/ # HarmonyOS API 实现
完整更新流程:
更新队列详细流程:
路径计算:
API统一封装:
3. Taro简单实现demo
Taro源码下载:github.com/NervJS/taro…
Taro deepwiki地址:
(用ai辅助实现效率更高)