从零开始理解跨平台架构的核心设计思想
目录
- 用生活案例理解分离架构
- React 的跨平台挑战
- 分离架构的核心设计
- 深入 Reconciler 层
- 深入 Renderer 层
- Host Config 接口设计
- 架构设计思想提炼
- 手写一个简易跨平台框架
- 实际应用场景
用生活案例理解分离架构 {#生活案例}
案例:餐厅的厨房与服务系统
想象一个连锁餐厅品牌,它要在不同场景开店:
┌─────────────────────────────────────────────────────────────┐
│ 同一个菜谱,不同的服务方式 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景 1:堂食餐厅 │
│ 厨房制作 → 服务员端上桌 → 客人堂食 │
│ │
│ 场景 2:外卖平台 │
│ 厨房制作 → 打包装盒 → 骑手配送 │
│ │
│ 场景 3:自助取餐 │
│ 厨房制作 → 保温柜 → 客人自取 │
│ │
└─────────────────────────────────────────────────────────────┘
关键问题:如何设计系统架构?
❌ 糟糕的设计:耦合架构
// 每个场景写一套代码
class DineInRestaurant {
prepareDish(order) {
// 做菜逻辑
const dish = this.cook(order);
// 堂食服务逻辑(耦合!)
this.waiter.serveToDiner(dish);
}
}
class DeliveryRestaurant {
prepareDish(order) {
// 做菜逻辑(重复代码!)
const dish = this.cook(order);
// 外卖逻辑
this.packDish(dish);
this.deliveryService.send(dish);
}
}
// 问题:
// 1. 做菜逻辑重复 → 难以维护
// 2. 菜谱更新需要改三个地方 → 容易出错
// 3. 新增场景(如自动贩卖机)→ 又要复制一套代码
✅ 优秀的设计:分离架构
// ============================================
// 核心层:厨房(Reconciler)
// 职责:只关心"做菜"的逻辑
// ============================================
class Kitchen {
prepareDish(order) {
// 1. 根据菜谱制作菜品
const dish = this.cook(order);
// 2. 通知服务层"菜做好了"
this.serviceAdapter.deliverDish(dish);
}
cook(order) {
// 核心做菜逻辑(所有场景共享)
return { name: order.dishName, status: 'ready' };
}
}
// ============================================
// 适配器接口(Host Config)
// 定义:服务层需要实现的标准接口
// ============================================
interface ServiceAdapter {
deliverDish(dish): void;
notifyCustomer(message): void;
cleanup(): void;
}
// ============================================
// 服务层 1:堂食(Renderer for Dine-In)
// ============================================
class DineInService implements ServiceAdapter {
deliverDish(dish) {
this.waiter.serveToDiner(dish);
}
notifyCustomer(message) {
this.waiter.announceToTable(message);
}
cleanup() {
this.waiter.clearTable();
}
}
// ============================================
// 服务层 2:外卖(Renderer for Delivery)
// ============================================
class DeliveryService implements ServiceAdapter {
deliverDish(dish) {
this.packDish(dish);
this.deliveryPlatform.dispatchRider(dish);
}
notifyCustomer(message) {
this.sms.send(message);
}
cleanup() {
// 外卖无需清理
}
}
// ============================================
// 使用:厨房不关心服务方式
// ============================================
const kitchen = new Kitchen();
// 堂食场景
kitchen.setServiceAdapter(new DineInService());
kitchen.prepareDish({ dishName: 'Pasta' });
// 外卖场景
kitchen.setServiceAdapter(new DeliveryService());
kitchen.prepareDish({ dishName: 'Pasta' });
// ✅ 厨房代码不变,只需切换服务层!
核心优势
┌─────────────────────────────────────────────────────────────┐
│ 分离架构的三大优势 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 代码复用(厨房逻辑只写一次) │
│ 做菜流程、菜谱管理 → 所有场景共享 │
│ │
│ 2. 易于扩展(新增场景不影响厨房) │
│ 新开自动售卖机 → 只需实现 ServiceAdapter │
│ │
│ 3. 职责清晰(关注点分离) │
│ 厨房:只管做菜 │
│ 服务层:只管交付 │
│ │
└─────────────────────────────────────────────────────────────┘
React 的跨平台挑战 {#跨平台挑战}
React 的目标
React 最初只为浏览器设计,但后来面临更多场景:
┌─────────────────────────────────────────────────────────────┐
│ React 需要支持的平台 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 🌐 Web (react-dom) │
│ 渲染到:浏览器 DOM │
│ API:document.createElement, appendChild │
│ │
│ 📱 iOS/Android (react-native) │
│ 渲染到:原生 UI 组件 │
│ API:UIView, TextView, etc. │
│ │
│ 🖥️ Desktop (react-native-windows) │
│ 渲染到:桌面原生 UI │
│ API:Win32 API, Cocoa │
│ │
│ 🖨️ PDF (react-pdf) │
│ 渲染到:PDF 文档 │
│ API:PDFKit │
│ │
│ 🧪 测试环境 (react-test-renderer) │
│ 渲染到:纯 JS 对象(不涉及真实 UI) │
│ │
└─────────────────────────────────────────────────────────────┘
核心矛盾
// React 的核心逻辑(所有平台通用)
// - 组件状态管理:useState, useReducer
// - 组件生命周期:useEffect, useLayoutEffect
// - 虚拟 DOM 树的计算:Diff 算法
// - 优先级调度:Scheduler
// React 的平台差异(每个平台不同)
// - 如何创建 UI 元素?
// Web: document.createElement('div')
// Native: new UIView()
//
// - 如何更新属性?
// Web: element.className = 'active'
// Native: view.backgroundColor = 'red'
//
// - 如何处理事件?
// Web: element.addEventListener('click', handler)
// Native: view.addGestureRecognizer(tapGesture)
// ❌ 如果不分离,React 代码会变成这样:
function commitUpdate(fiber) {
if (platform === 'web') {
updateDOMProperties(fiber.stateNode, fiber.props);
} else if (platform === 'native') {
updateNativeViewProps(fiber.stateNode, fiber.props);
} else if (platform === 'pdf') {
updatePDFNode(fiber.stateNode, fiber.props);
}
// 💥 每新增一个平台,核心代码就要改一次!
}
分离架构的核心设计 {#核心设计}
React 的三层架构
┌─────────────────────────────────────────────────────────────┐
│ 应用层(开发者代码) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ function App() { │ │
│ │ const [count, setCount] = useState(0); │ │
│ │ return <div>{count}</div>; │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
使用 React API
↓
┌─────────────────────────────────────────────────────────────┐
│ 核心层:react 包(平台无关) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ • React.createElement() │ │
│ │ • useState, useEffect, useContext │ │
│ │ • Component, PureComponent │ │
│ │ • Children, memo, lazy │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
生成虚拟 DOM(React Element)
↓
┌─────────────────────────────────────────────────────────────┐
│ 协调层:react-reconciler(平台无关的核心逻辑) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Reconciler 的职责: │ │
│ │ 1. 维护 Fiber 树(虚拟树结构) │ │
│ │ 2. 执行 Diff 算法(计算变化) │ │
│ │ 3. 任务调度(时间切片、优先级) │ │
│ │ 4. 调用 Hooks(useState, useEffect 的实现) │ │
│ │ 5. 标记副作用(Placement, Update, Deletion) │ │
│ │ │ │
│ │ ⚠️ 关键:Reconciler 不知道"具体平台"是什么 │ │
│ │ 它只知道"需要执行什么操作" │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
通过 Host Config 接口调用
↓
┌─────────────────────────────────────────────────────────────┐
│ 渲染层:Renderer(平台特定的实现) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ react-dom │ │ react-native │ │ react-pdf │ │
│ │ │ │ │ │ │ │
│ │ 实现: │ │ 实现: │ │ 实现: │ │
│ │ createInst.. │ │ createInst.. │ │ createInst.. │ │
│ │ appendChild │ │ appendChild │ │ appendChild │ │
│ │ commitUpdate │ │ commitUpdate │ │ commitUpdate │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
↓
┌─────────────────────────────────────────────────────────────┐
│ 宿主环境 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 浏览器 DOM │ │ Native UI │ │ PDF 文档 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
依赖关系(依赖倒置)
传统依赖(高层依赖低层):
┌────────────┐
│ React │
│ (核心逻辑) │
└─────┬──────┘
│ 依赖
↓
┌────────────┐
│ React DOM │
│ (具体实现) │
└────────────┘
问题:核心代码依赖具体实现 → 无法扩展
依赖倒置(低层依赖高层的抽象):
┌────────────────────────────────┐
│ React Reconciler │
│ (定义抽象接口) │
│ ┌──────────────────────────┐ │
│ │ Host Config Interface │ │
│ │ - createInstance() │ │
│ │ - appendChild() │ │
│ │ - commitUpdate() │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
↑ ↑
│ 实现 │ 实现
│ │
┌────────┴──────┐ ┌─────┴────────┐
│ React DOM │ │ React Native │
│ (实现接口) │ │ (实现接口) │
└───────────────┘ └──────────────┘
优势:
✅ 核心层不依赖具体平台
✅ 新增平台只需实现接口
✅ 核心层和平台层可独立演进
深入 Reconciler 层 {#reconciler层}
Reconciler 的核心职责
// ============================================
// react-reconciler 包的核心代码(简化版)
// ============================================
// Reconciler 只关心"算法逻辑",不关心"具体平台"
function performUnitOfWork(fiber) {
// 1. beginWork:向下遍历,计算新的虚拟树
const next = beginWork(fiber);
if (next !== null) {
return next; // 有子节点,继续向下
}
// 2. completeWork:向上回溯,构建真实节点
let completedWork = fiber;
while (completedWork !== null) {
completeUnitOfWork(completedWork);
if (completedWork.sibling !== null) {
return completedWork.sibling; // 处理兄弟节点
}
completedWork = completedWork.return; // 返回父节点
}
return null; // 遍历完成
}
// ============================================
// completeWork:构建真实节点
// ============================================
function completeWork(fiber) {
const { type, props } = fiber;
switch (fiber.tag) {
case HostComponent: // 宿主组件(如 div, View)
if (fiber.stateNode === null) {
// ✨ 关键:通过 Host Config 创建节点
// Reconciler 不知道 hostConfig.createInstance 的具体实现
// Web 平台:document.createElement('div')
// Native 平台:new UIView()
const instance = hostConfig.createInstance(
type, // 'div' or 'View'
props, // { className: 'container' }
fiber
);
// 将子节点挂载到当前节点
appendAllChildren(instance, fiber);
// 保存到 Fiber 节点
fiber.stateNode = instance;
} else {
// 节点已存在,需要更新
// ✨ 通过 Host Config 准备更新
const updatePayload = hostConfig.prepareUpdate(
fiber.stateNode,
type,
fiber.alternate.memoizedProps, // 旧 props
props // 新 props
);
if (updatePayload) {
// 标记需要更新
fiber.updateQueue = updatePayload;
fiber.flags |= Update;
}
}
break;
case FunctionComponent:
// 函数组件没有真实节点
break;
}
}
// ============================================
// Commit 阶段:执行副作用
// ============================================
function commitWork(fiber) {
switch (fiber.flags) {
case Placement: // 插入节点
// ✨ 通过 Host Config 执行插入操作
const parent = getHostParent(fiber);
hostConfig.appendChild(parent, fiber.stateNode);
break;
case Update: // 更新节点
// ✨ 通过 Host Config 执行更新操作
const updatePayload = fiber.updateQueue;
hostConfig.commitUpdate(
fiber.stateNode,
updatePayload,
fiber.type,
fiber.memoizedProps
);
break;
case Deletion: // 删除节点
// ✨ 通过 Host Config 执行删除操作
hostConfig.removeChild(parent, fiber.stateNode);
break;
}
}
Reconciler 的"无知"是优势
┌─────────────────────────────────────────────────────────────┐
│ Reconciler 只知道"做什么",不知道"怎么做" │
├─────────────────────────────────────────────────────────────┤
│ │
│ Reconciler 说: │
│ "我需要创建一个类型为 'div' 的节点,属性是 {...}" │
│ │
│ React DOM 说: │
│ "好的,我用 document.createElement('div') 创建" │
│ │
│ React Native 说: │
│ "好的,我用 new UIView() 创建" │
│ │
│ React PDF 说: │
│ "好的,我用 PDFKit.rect() 创建" │
│ │
│ ✅ Reconciler 的代码完全不变! │
│ │
└─────────────────────────────────────────────────────────────┘
深入 Renderer 层 {#renderer层}
React DOM 的实现
// ============================================
// react-dom 包的核心实现(简化版)
// ============================================
// React DOM 实现 Host Config 接口
const ReactDOMHostConfig = {
// 1. 创建 DOM 节点
createInstance(type, props) {
const element = document.createElement(type);
// 设置初始属性
Object.keys(props).forEach(key => {
if (key === 'children') return;
if (key === 'className') {
element.className = props[key];
} else if (key.startsWith('on')) {
// 事件监听
const eventType = key.toLowerCase().slice(2);
element.addEventListener(eventType, props[key]);
} else {
element.setAttribute(key, props[key]);
}
});
return element; // 返回真实 DOM 节点
},
// 2. 创建文本节点
createTextInstance(text) {
return document.createTextNode(text);
},
// 3. 将子节点挂载到父节点
appendChild(parent, child) {
parent.appendChild(child);
},
// 4. 插入节点到指定位置
insertBefore(parent, child, beforeChild) {
parent.insertBefore(child, beforeChild);
},
// 5. 删除节点
removeChild(parent, child) {
parent.removeChild(child);
},
// 6. 计算需要更新的属性
prepareUpdate(instance, type, oldProps, newProps) {
// 对比新旧 props,返回差异
const updatePayload = [];
Object.keys(oldProps).forEach(key => {
if (key === 'children' || newProps.hasOwnProperty(key)) return;
// 旧属性在新 props 中不存在 → 删除
updatePayload.push(key, null);
});
Object.keys(newProps).forEach(key => {
if (key === 'children') return;
if (oldProps[key] !== newProps[key]) {
// 属性值变化 → 更新
updatePayload.push(key, newProps[key]);
}
});
return updatePayload.length > 0 ? updatePayload : null;
},
// 7. 执行更新
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
// 应用 updatePayload
for (let i = 0; i < updatePayload.length; i += 2) {
const key = updatePayload[i];
const value = updatePayload[i + 1];
if (key === 'className') {
instance.className = value;
} else if (key.startsWith('on')) {
// 事件更新(简化处理)
const eventType = key.toLowerCase().slice(2);
instance.removeEventListener(eventType, oldProps[key]);
if (value) {
instance.addEventListener(eventType, value);
}
} else if (value === null) {
instance.removeAttribute(key);
} else {
instance.setAttribute(key, value);
}
}
},
// 8. 获取容器的根节点
getRootHostContext(rootContainer) {
return { namespace: 'html' };
},
// 9. 获取子节点的上下文
getChildHostContext(parentContext, type) {
if (type === 'svg') {
return { namespace: 'svg' };
}
return parentContext;
},
// 10. 判断是否需要设置文本内容
shouldSetTextContent(type, props) {
return (
typeof props.children === 'string' ||
typeof props.children === 'number'
);
},
// 其他方法...
};
// ============================================
// 创建 Reconciler 实例
// ============================================
import Reconciler from 'react-reconciler';
const ReactDOMReconciler = Reconciler(ReactDOMHostConfig);
// ============================================
// ReactDOM.render 的实现
// ============================================
const ReactDOM = {
render(element, container) {
// 创建根 Fiber
const root = ReactDOMReconciler.createContainer(container);
// 开始渲染
ReactDOMReconciler.updateContainer(element, root, null);
}
};
React Native 的实现
// ============================================
// react-native-renderer 的实现(简化版)
// ============================================
const ReactNativeHostConfig = {
// 1. 创建原生 View
createInstance(type, props) {
// type: 'View', 'Text', 'Image', etc.
// 调用原生模块创建 UI 组件
const viewTag = UIManager.createView(
type, // 组件类型
rootTag, // 根容器
props // 初始属性
);
return {
viewTag, // 原生组件的唯一 ID
type,
props
};
},
// 2. 创建文本节点
createTextInstance(text) {
const viewTag = UIManager.createView('RCTRawText', rootTag, {
text: text
});
return { viewTag, text };
},
// 3. 将子节点挂载到父节点
appendChild(parent, child) {
UIManager.manageChildren(
parent.viewTag, // 父节点 ID
null, // 移动操作
null, // 移除操作
[child.viewTag], // 添加的子节点 ID
[parent.children.length], // 插入位置
null
);
parent.children.push(child);
},
// 4. 更新属性
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
// 通知原生层更新属性
UIManager.updateView(
instance.viewTag,
type,
updatePayload // 变化的属性
);
},
// 5. 删除节点
removeChild(parent, child) {
UIManager.manageChildren(
parent.viewTag,
null,
[parent.children.indexOf(child)], // 移除的索引
null,
null,
null
);
parent.children.splice(parent.children.indexOf(child), 1);
},
// 其他方法实现...
};
// ✅ 同样是 Reconciler,不同的 Host Config!
const ReactNativeReconciler = Reconciler(ReactNativeHostConfig);
对比两个 Renderer
┌─────────────────────────────────────────────────────────────┐
│ React DOM vs React Native │
├─────────────────────────────────────────────────────────────┤
│ │
│ 相同的接口方法: │
│ createInstance, appendChild, commitUpdate, ... │
│ │
│ 不同的实现细节: │
│ │
│ React DOM: │
│ createInstance → document.createElement('div') │
│ appendChild → parent.appendChild(child) │
│ commitUpdate → element.className = 'active' │
│ │
│ React Native: │
│ createInstance → UIManager.createView('View') │
│ appendChild → UIManager.manageChildren(...) │
│ commitUpdate → UIManager.updateView(...) │
│ │
│ ✅ Reconciler 调用的是相同的方法名 │
│ 但底层执行的是不同平台的 API │
│ │
└─────────────────────────────────────────────────────────────┘
Host Config 接口设计 {#hostconfig}
完整的 Host Config 接口
// ============================================
// Host Config 接口定义(官方文档)
// ============================================
interface HostConfig<
Type, // 节点类型(如 'div', 'View')
Props, // 节点属性
Container, // 根容器类型
Instance, // 节点实例
TextInstance, // 文本节点实例
...
> {
// ==============================
// 1. 节点创建与删除
// ==============================
/**
* 创建节点实例
* @param type - 节点类型(如 'div', 'View')
* @param props - 节点属性
* @param rootContainer - 根容器
* @param hostContext - 宿主上下文
* @returns 节点实例
*/
createInstance(
type: Type,
props: Props,
rootContainer: Container,
hostContext: HostContext
): Instance;
/**
* 创建文本节点
* @param text - 文本内容
* @param rootContainer - 根容器
* @param hostContext - 宿主上下文
* @returns 文本节点实例
*/
createTextInstance(
text: string,
rootContainer: Container,
hostContext: HostContext
): TextInstance;
// ==============================
// 2. 树结构操作
// ==============================
/**
* 将子节点添加到父节点
*/
appendChild(parent: Instance, child: Instance | TextInstance): void;
/**
* 在指定节点前插入子节点
*/
insertBefore(
parent: Instance,
child: Instance | TextInstance,
beforeChild: Instance | TextInstance
): void;
/**
* 删除子节点
*/
removeChild(parent: Instance, child: Instance | TextInstance): void;
/**
* 在初次渲染时,将所有子节点挂载到父节点
* (在 completeWork 阶段调用)
*/
appendInitialChild(parent: Instance, child: Instance | TextInstance): void;
// ==============================
// 3. 属性更新
// ==============================
/**
* 准备更新:计算新旧 props 的差异
* @returns 返回 updatePayload(差异对象)或 null
*/
prepareUpdate(
instance: Instance,
type: Type,
oldProps: Props,
newProps: Props,
rootContainer: Container,
hostContext: HostContext
): UpdatePayload | null;
/**
* 提交更新:应用 updatePayload
*/
commitUpdate(
instance: Instance,
updatePayload: UpdatePayload,
type: Type,
oldProps: Props,
newProps: Props
): void;
/**
* 提交文本更新
*/
commitTextUpdate(
textInstance: TextInstance,
oldText: string,
newText: string
): void;
// ==============================
// 4. 上下文管理
// ==============================
/**
* 获取根容器的上下文
* 例如:HTML 中的 namespace(html/svg)
*/
getRootHostContext(rootContainer: Container): HostContext;
/**
* 获取子节点的上下文
* 例如:进入 <svg> 时切换 namespace
*/
getChildHostContext(
parentHostContext: HostContext,
type: Type
): HostContext;
// ==============================
// 5. 特殊处理
// ==============================
/**
* 判断是否应该直接设置文本内容
* 优化:如果子节点只有纯文本,直接用 textContent 设置
*/
shouldSetTextContent(type: Type, props: Props): boolean;
/**
* 重置文本内容
* 在需要将文本节点替换为元素节点时调用
*/
resetTextContent(instance: Instance): void;
// ==============================
// 6. 宿主环境相关
// ==============================
/**
* 调度延迟回调(类似 requestIdleCallback)
*/
scheduleTimeout(
fn: () => void,
delay: number
): TimeoutHandle;
/**
* 取消延迟回调
*/
cancelTimeout(handle: TimeoutHandle): void;
/**
* 获取当前时间(用于调度器)
*/
now(): number;
/**
* 判断是否支持 Mutation(修改 DOM)
* 如果为 false,需要实现 Persistent 模式的接口
*/
supportsMutation: boolean;
/**
* 判断是否支持 Hydration(SSR 水合)
*/
supportsHydration: boolean;
// ==============================
// 7. Hydration 相关(SSR)
// ==============================
/**
* 尝试复用服务端渲染的节点
*/
canHydrateInstance?(
instance: HydratableInstance,
type: Type,
props: Props
): boolean;
/**
* 水合节点
*/
hydrateInstance?(
instance: Instance,
type: Type,
props: Props,
rootContainer: Container
): UpdatePayload | null;
// 其他方法...
}
Host Config 的设计哲学
┌─────────────────────────────────────────────────────────────┐
│ Host Config 的设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 最小化接口(只暴露必要的方法) │
│ 不需要暴露平台的所有细节,只需实现渲染所需的操作 │
│ │
│ 2. 平台无关的命名 │
│ 方法名不包含具体平台的概念 │
│ ✅ createInstance(通用) │
│ ❌ createDOMElement(特定) │
│ │
│ 3. 分离关注点 │
│ 创建、更新、删除 → 独立的方法 │
│ 不混杂业务逻辑 │
│ │
│ 4. 可选的扩展点 │
│ supportsHydration → 不是所有平台都需要 SSR │
│ supportsMutation → 有些平台是不可变的(如 Figma) │
│ │
└─────────────────────────────────────────────────────────────┘
架构设计思想提炼 {#设计思想}
1. 依赖倒置原则(Dependency Inversion Principle)
传统依赖:高层模块依赖低层模块
┌──────────────┐
│ 高层业务 │ ──depends on──┐
└──────────────┘ ↓
┌──────────────┐
│ 具体实现 │
└──────────────┘
问题:业务逻辑与具体实现耦合,难以替换
依赖倒置:双方都依赖抽象
┌──────────────┐ ┌──────────────┐
│ 高层业务 │ ──依赖──→ │ 抽象接口 │
└──────────────┘ └──────────────┘
↑
│ 实现
┌──────────────┴─────┐
│ 具体实现 │
└────────────────────┘
优势:
✅ 高层模块不依赖具体实现
✅ 具体实现可以随意替换
✅ 新增实现不影响高层
React 中的应用:
Reconciler(高层) ←依赖→ Host Config(抽象)
↑
│ 实现
┌───────────────┼───────────────┐
│ │ │
React DOM React Native React PDF
2. 开闭原则(Open-Closed Principle)
定义:对扩展开放,对修改关闭
❌ 违反开闭原则:
function render(element) {
if (platform === 'web') {
renderToDOM(element);
} else if (platform === 'native') {
renderToNative(element);
} else if (platform === 'pdf') {
renderToPDF(element);
}
// 每次新增平台都要修改这个函数
}
✅ 遵循开闭原则:
// 定义抽象
interface Renderer {
render(element): void;
}
// 核心逻辑不变
function performWork(element, renderer: Renderer) {
const tree = buildTree(element);
renderer.render(tree); // 多态
}
// 扩展:新增平台只需实现接口
class WebRenderer implements Renderer {
render(tree) { /* DOM 渲染 */ }
}
class NativeRenderer implements Renderer {
render(tree) { /* Native 渲染 */ }
}
class PDFRenderer implements Renderer {
render(tree) { /* PDF 渲染 */ }
}
// ✅ 核心代码(performWork)不需要修改!
3. 适配器模式(Adapter Pattern)
定义:将一个类的接口转换成客户期望的另一个接口
React 中的体现:
┌─────────────────────────────────────────────────────────────┐
│ Reconciler 期望的接口 │
│ createInstance(type, props) → Instance │
│ appendChild(parent, child) → void │
└─────────────────────────────────────────────────────────────┘
↑
│ 适配
│
┌─────────────────────────────────────────────────────────────┐
│ Web 平台的原生 API │
│ document.createElement(tagName) → HTMLElement │
│ parentNode.appendChild(childNode) → Node │
└─────────────────────────────────────────────────────────────┘
// Adapter 实现
const WebAdapter = {
createInstance(type, props) {
return document.createElement(type); // 适配
},
appendChild(parent, child) {
parent.appendChild(child); // 直接映射
}
};
// ✅ Reconciler 不关心底层是 document.createElement
// 它只知道调用 createInstance
4. 策略模式(Strategy Pattern)
定义:定义一系列算法,将每个算法封装起来,使它们可以互换
React 中的应用:
// 策略接口
interface RenderStrategy {
render(element): void;
}
// 具体策略 1
class DOMRenderStrategy implements RenderStrategy {
render(element) { /* DOM 渲染逻辑 */ }
}
// 具体策略 2
class NativeRenderStrategy implements RenderStrategy {
render(element) { /* Native 渲染逻辑 */ }
}
// 上下文(使用策略)
class Reconciler {
constructor(private strategy: RenderStrategy) {}
performWork(element) {
// 核心逻辑
const tree = this.buildTree(element);
// 使用策略渲染
this.strategy.render(tree);
}
}
// 运行时切换策略
const domReconciler = new Reconciler(new DOMRenderStrategy());
const nativeReconciler = new Reconciler(new NativeRenderStrategy());
5. 关注点分离(Separation of Concerns)
┌─────────────────────────────────────────────────────────────┐
│ 关注点分离的三个层次 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: React 包(声明式 UI) │
│ 关注点:让开发者用 JSX 描述 UI │
│ 不关心:UI 如何计算、如何渲染 │
│ │
│ Layer 2: Reconciler(协调与调度) │
│ 关注点:计算 UI 的变化、调度更新 │
│ 不关心:具体平台的渲染细节 │
│ │
│ Layer 3: Renderer(平台渲染) │
│ 关注点:将变化渲染到具体平台 │
│ 不关心:UI 是如何计算出来的 │
│ │
└─────────────────────────────────────────────────────────────┘
每一层只关心自己的职责,互不干扰
手写一个简易跨平台框架 {#实战案例}
让我们从零实现一个迷你版的跨平台框架,加深理解。
第一步:定义核心抽象(类似 React)
// ============================================
// 1. 定义虚拟节点(类似 React Element)
// ============================================
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.flat()
}
};
}
// 使用示例
const vdom = createElement('div', { id: 'container' },
createElement('h1', null, 'Hello'),
createElement('p', null, 'World')
);
// 结果:
// {
// type: 'div',
// props: {
// id: 'container',
// children: [
// { type: 'h1', props: { children: ['Hello'] } },
// { type: 'p', props: { children: ['World'] } }
// ]
// }
// }
第二步:实现协调器(类似 Reconciler)
// ============================================
// 2. 实现 Reconciler(平台无关)
// ============================================
class Reconciler {
constructor(hostConfig) {
this.hostConfig = hostConfig; // 依赖注入
}
// 递归渲染虚拟节点
render(vdom, container) {
// 清空容器(简化处理)
this.hostConfig.clearContainer(container);
// 渲染根节点
const instance = this.renderElement(vdom);
// 挂载到容器
this.hostConfig.appendChild(container, instance);
}
// 渲染单个元素
renderElement(vdom) {
// 处理文本节点
if (typeof vdom === 'string' || typeof vdom === 'number') {
return this.hostConfig.createTextInstance(String(vdom));
}
// 创建节点实例(通过 Host Config)
const instance = this.hostConfig.createInstance(
vdom.type,
vdom.props
);
// 递归渲染子节点
const children = vdom.props.children || [];
children.forEach(child => {
const childInstance = this.renderElement(child);
this.hostConfig.appendChild(instance, childInstance);
});
return instance;
}
// 更新逻辑(简化版)
update(oldVdom, newVdom, container) {
// 类型不同 → 重新渲染
if (oldVdom.type !== newVdom.type) {
return this.render(newVdom, container);
}
// 更新节点(简化:只处理属性)
const instance = container.firstChild;
this.hostConfig.updateInstance(
instance,
oldVdom.props,
newVdom.props
);
// 更新子节点(简化:假设子节点数量相同)
const oldChildren = oldVdom.props.children || [];
const newChildren = newVdom.props.children || [];
oldChildren.forEach((oldChild, index) => {
if (newChildren[index]) {
this.update(oldChild, newChildren[index], instance);
}
});
}
}
第三步:实现 Web 渲染器
// ============================================
// 3. 实现 Web Renderer(类似 react-dom)
// ============================================
const WebHostConfig = {
// 创建元素
createInstance(type, props) {
const element = document.createElement(type);
// 设置属性
Object.keys(props).forEach(key => {
if (key === 'children') return;
if (key === 'className') {
element.className = props[key];
} else if (key.startsWith('on')) {
const eventType = key.toLowerCase().substring(2);
element.addEventListener(eventType, props[key]);
} else {
element.setAttribute(key, props[key]);
}
});
return element;
},
// 创建文本节点
createTextInstance(text) {
return document.createTextNode(text);
},
// 添加子节点
appendChild(parent, child) {
parent.appendChild(child);
},
// 清空容器
clearContainer(container) {
container.innerHTML = '';
},
// 更新实例
updateInstance(instance, oldProps, newProps) {
// 简化:只处理 className
if (oldProps.className !== newProps.className) {
instance.className = newProps.className;
}
}
};
// 创建 Web Reconciler
const WebReconciler = new Reconciler(WebHostConfig);
// 暴露给开发者的 API
const MyReactDOM = {
render(vdom, container) {
WebReconciler.render(vdom, container);
}
};
第四步:实现 Canvas 渲染器
// ============================================
// 4. 实现 Canvas Renderer(跨平台示例)
// ============================================
const CanvasHostConfig = {
// 创建 Canvas 节点(用对象表示)
createInstance(type, props) {
return {
type,
props,
children: [],
x: props.x || 0,
y: props.y || 0
};
},
// 创建文本节点
createTextInstance(text) {
return { type: 'text', text, children: [] };
},
// 添加子节点
appendChild(parent, child) {
parent.children.push(child);
},
// 清空容器(重绘 Canvas)
clearContainer(container) {
const ctx = container.getContext('2d');
ctx.clearRect(0, 0, container.width, container.height);
},
// 更新实例
updateInstance(instance, oldProps, newProps) {
instance.props = newProps;
instance.x = newProps.x || instance.x;
instance.y = newProps.y || instance.y;
},
// ✨ 额外方法:绘制到 Canvas
paint(node, ctx, x = 0, y = 0) {
if (node.type === 'rect') {
ctx.fillStyle = node.props.color || 'black';
ctx.fillRect(
x + node.x,
y + node.y,
node.props.width,
node.props.height
);
} else if (node.type === 'text') {
ctx.fillStyle = node.props.color || 'black';
ctx.fillText(node.text, x, y);
}
// 递归绘制子节点
node.children.forEach(child => {
this.paint(child, ctx, x + node.x, y + node.y);
});
}
};
// 创建 Canvas Reconciler
const CanvasReconciler = new Reconciler(CanvasHostConfig);
// 暴露给开发者的 API
const MyReactCanvas = {
render(vdom, canvas) {
CanvasReconciler.render(vdom, canvas);
// 绘制到 Canvas
const ctx = canvas.getContext('2d');
const tree = canvas.firstChild; // 简化:直接取第一个子节点
CanvasHostConfig.paint(tree, ctx);
}
};
第五步:使用示例
// ============================================
// 5. 使用我们的框架(同一套代码,不同平台)
// ============================================
// 定义组件(平台无关)
const App = createElement('div', { className: 'app' },
createElement('h1', null, 'Hello World'),
createElement('p', null, 'This is a cross-platform framework!')
);
// 渲染到 Web
const webContainer = document.getElementById('root');
MyReactDOM.render(App, webContainer);
// 渲染到 Canvas
const App2 = createElement('rect', { x: 10, y: 10, width: 200, height: 100, color: 'blue' },
createElement('text', { x: 20, y: 30, color: 'white' }, 'Hello Canvas!')
);
const canvasContainer = document.getElementById('canvas');
MyReactCanvas.render(App2, canvasContainer);
// ✅ 同样的 Reconciler 逻辑
// ✅ 不同的 Renderer 实现
// ✅ 一套代码,多端运行!
架构图总结
开发者代码(平台无关)
createElement('div', ...)
↓
┌───────────────────────────────────────────┐
│ Reconciler(平台无关的协调逻辑) │
│ • renderElement() │
│ • update() │
│ • 调用 hostConfig.createInstance() │
└───────────────────────────────────────────┘
↓ 通过 Host Config 接口
┌───────────────────────────────────────────┐
│ Renderer(平台特定的实现) │
│ ┌─────────────┐ ┌─────────────┐ │
│ │WebHostConfig│ │CanvasConfig │ │
│ │document. │ │canvas. │ │
│ │createElement│ │fillRect │ │
│ └─────────────┘ └─────────────┘ │
└───────────────────────────────────────────┘
↓ ↓
┌──────────┐ ┌──────────┐
│ DOM │ │ Canvas │
└──────────┘ └──────────┘
实际应用场景 {#应用场景}
1. 跨端框架(Taro / Uni-app)
┌─────────────────────────────────────────────────────────────┐
│ Taro 的跨平台架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 开发者用 React 语法编写代码 │
│ ↓ │
│ Taro Runtime(类似 Reconciler) │
│ - 解析 React 组件 │
│ - 构建虚拟 DOM 树 │
│ - 计算差异 │
│ ↓ 通过适配层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 微信小程序│ │ 支付宝小程│ │ H5 │ │
│ │ Renderer │ │程Renderer│ │ Renderer │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ↓ ↓ ↓ │
│ setData() setData() DOM API │
│ │
└─────────────────────────────────────────────────────────────┘
核心思想:
✅ 一套代码(React 组件)
✅ 多个 Renderer(小程序、H5、App)
✅ 通过适配层抹平差异
2. 测试框架(Jest / React Testing Library)
// ============================================
// react-test-renderer:不渲染真实 UI
// ============================================
const TestHostConfig = {
createInstance(type, props) {
// 返回纯 JS 对象,不创建真实 DOM
return {
type,
props,
children: []
};
},
appendChild(parent, child) {
parent.children.push(child);
},
// 其他方法返回空实现
};
// 使用
import { create } from 'react-test-renderer';
const App = () => <div>Hello</div>;
const tree = create(<App />).toJSON();
console.log(tree);
// {
// type: 'div',
// props: {},
// children: ['Hello']
// }
// ✅ 不需要真实浏览器环境
// ✅ 测试速度快
// ✅ 可以断言 UI 结构
3. 自定义渲染器(Figma / Sketch)
// ============================================
// react-figma:渲染到 Figma 设计稿
// ============================================
const FigmaHostConfig = {
createInstance(type, props) {
let node;
switch (type) {
case 'frame':
node = figma.createFrame();
break;
case 'text':
node = figma.createText();
break;
case 'rectangle':
node = figma.createRectangle();
break;
}
// 设置属性
if (props.width) node.resize(props.width, props.height);
if (props.fill) node.fills = [{ type: 'SOLID', color: props.fill }];
return node;
},
appendChild(parent, child) {
parent.appendChild(child);
},
// ...
};
// 使用 React 编写 Figma 插件!
const DesignSystem = () => (
<frame width={300} height={200}>
<text fontSize={24}>Title</text>
<rectangle width={100} height={50} fill={{ r: 1, g: 0, b: 0 }} />
</frame>
);
4. 跨平台 UI 库(Ant Design / Material-UI)
// ============================================
// 组件库的跨平台架构
// ============================================
// 核心逻辑(平台无关)
class Button {
constructor(props) {
this.props = props;
}
handleClick = () => {
this.props.onClick?.();
};
// 抽象方法(子类实现)
render() {
throw new Error('Must implement render()');
}
}
// Web 平台实现
class WebButton extends Button {
render() {
return (
<button onClick={this.handleClick}>
{this.props.children}
</button>
);
}
}
// Native 平台实现
class NativeButton extends Button {
render() {
return (
<TouchableOpacity onPress={this.handleClick}>
<Text>{this.props.children}</Text>
</TouchableOpacity>
);
}
}
// 平台检测
const Button = Platform.select({
web: WebButton,
native: NativeButton
});
// ✅ 开发者使用统一 API
<Button onClick={() => console.log('clicked')}>
Click me
</Button>
总结:核心理念再回顾
┌─────────────────────────────────────────────────────────────┐
│ Reconciler 与 Renderer 分离的核心价值 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 约束条件: │
│ • 多平台(Web、Native、Canvas、PDF...) │
│ • 每个平台 API 完全不同 │
│ • 核心逻辑需要复用 │
│ │
│ 架构设计: │
│ • 关注点分离(Reconciler 管算法,Renderer 管渲染) │
│ • 依赖倒置(核心层定义接口,平台层实现) │
│ • 适配器模式(Host Config 抹平平台差异) │
│ • 策略模式(运行时切换渲染策略) │
│ │
│ 实现目标: │
│ • 一套核心代码 │
│ • 多端运行 │
│ • 易于扩展(新增平台不改核心) │
│ • 可测试(可以不依赖真实 UI) │
│ │
└─────────────────────────────────────────────────────────────┘
核心理念:
在约束条件下(多平台、API 差异大),
通过合理的架构设计(分层、抽象、依赖倒置),
实现复杂目标(代码复用、易扩展、可维护)。
通过学习这个架构,你可以应用到:
- 跨端框架设计:小程序、App、Web 统一开发
- 通用 SDK 设计:一套代码,多平台运行
- 插件系统设计:核心稳定,插件灵活扩展
- 微服务架构:业务逻辑与基础设施解耦
关键思想:不要让核心逻辑依赖具体实现,而是让具体实现依赖抽象接口!