各位同仁,各位技术爱好者,大家好!
今天,我们来探讨一个看似激进,实则充满潜力的主题:如何在资源受限、功耗敏感的嵌入式设备上运行 React,并且实现极致的裁剪,以节省宝贵的内存。当提到 React,很多人脑海中浮现的是复杂的构建工具、庞大的运行时和现代浏览器的强大能力。然而,React 的核心思想——组件化、声明式 UI 和高效的协调算法——对于嵌入式设备的用户界面开发同样具有巨大的吸引力。
想象一下,在一个内存只有几十甚至几兆字节的设备上,如何让 React 跑起来?这需要我们对 React 的内部机制有深刻的理解,并敢于对其进行外科手术式的精简。我们将聚焦于两个最主要的内存和性能开销来源:并发模式(Concurrent Mode)和 事件系统(Synthetic Event System)。我们将深入剖析它们的工作原理,解释为何它们在嵌入式场景下显得臃肿,并提供具体的裁剪策略和代码示例。
1. React 的核心与嵌入式设备的挑战
在深入裁剪之前,我们首先要明确 React 的核心价值。它提供了一套声明式的 API 来描述 UI 的状态,通过 Virtual DOM 和协调(Reconciliation)算法,高效地更新真实 DOM(或其他渲染目标)。这些核心机制,如 JSX、组件化、状态管理、生命周期(或 Hooks),对于构建可维护、可预测的 UI 来说,都是极具价值的。
然而,当我们将目光投向低功耗嵌入式设备时,我们面临着截然不同的挑战:
- 内存限制: 通常只有几兆字节的 RAM,任何额外的运行时开销都是不可接受的。
- CPU 限制: 主频较低,浮点运算能力弱,复杂的算法和频繁的任务切换会迅速耗尽计算资源。
- 功耗限制: 频繁的 CPU 活跃和内存访问会增加功耗,这对于电池供电设备是致命的。
- 环境差异: 没有完整的浏览器环境,没有 DOM API,也没有
requestAnimationFrame这样的调度原语。
React 的设计初衷是为了解决 Web 浏览器中的复杂 UI 性能问题。为了实现这一目标,它引入了许多高级特性,如并发模式和其内部的事件系统,这些特性在嵌入式设备上往往是过度设计的,并且带来了不必要的内存和 CPU 开销。
我们的目标是保留 React 声明式 UI 的优点,同时剥离那些为 Web 环境优化但对嵌入式设备而言是负担的特性。
2. 深入剖析与裁剪:并发模式 (Concurrent Mode)
并发模式是 React 18 引入的一项革命性功能,旨在通过可中断的渲染(Interruptible Rendering)和时间切片(Time Slicing)来改善用户体验。它的核心思想是允许 React 在后台渲染更新,同时保持主线程的响应,避免 UI 卡顿。
2.1 并发模式的工作原理及其开销
并发模式的实现依赖于几个关键组件和机制:
- 调度器 (Scheduler): React 内部有一个独立的
scheduler包,它利用浏览器提供的MessageChannel或requestAnimationFrame来实现宏任务和微任务的调度,从而实现时间切片。它维护一个优先级队列,根据任务的优先级来决定何时执行、何时暂停。 - 双缓冲树 (Double Buffering): 为了实现可中断渲染,React 需要维护多棵 Virtual DOM 树。在并发模式下,除了当前的 "work-in-progress" 树(正在构建的树)和 "current" 树(当前屏幕上显示的树)之外,可能还有其他中间状态的树或任务相关的元数据。这意味着更多的内存占用。
- 任务管理和优先级: React 内部需要复杂的逻辑来管理不同优先级的更新任务,包括区分紧急更新(如用户输入)和非紧急更新(如数据获取)。这涉及到任务队列、过期时间计算以及任务状态的追踪。
- 副作用列表和链表: 在协调过程中,React 会构建一个副作用列表(Effect List),其中包含需要对实际 DOM 进行操作的所有变更(插入、更新、删除)。在并发模式下,这些列表的管理更为复杂,以支持中断和恢复。
内存和 CPU 开销:
- 额外的 Virtual DOM 树: 双缓冲和其他中间状态的维护会显著增加内存占用。
- 调度器数据结构: 优先级队列、任务对象、定时器等都需要内存。
- 复杂的控制流: 任务的中断、恢复、优先级切换、依赖跟踪等逻辑会增加 CPU 负担。
- 浏览器 API 依赖: 对
MessageChannel和requestAnimationFrame的依赖意味着需要提供相应的 polyfill 或模拟,这本身就是额外的代码和潜在的运行时开销。
2.2 为何在嵌入式设备上移除并发模式?
在低功耗嵌入式设备上,并发模式带来的好处往往微不足道,甚至弊大于利:
- 简单 UI,无复杂交互: 嵌入式设备的 UI 通常功能专一,交互模式相对简单,很少有需要复杂时间切片来避免卡顿的场景。
- 内存是王道: 额外的 Virtual DOM 树和调度器数据结构会迅速耗尽有限的内存。
- 同步更新更可控: 在资源受限的环境中,完全同步的、阻塞式的更新模型反而更容易理解和控制,开发者可以精确地知道何时发生渲染,何时释放 CPU。
- 无浏览器环境: 缺乏
MessageChannel和requestAnimationFrame等浏览器原生 API,需要自行实现或模拟,增加了复杂性。
2.3 裁剪并发模式的实践
裁剪并发模式的核心思想是将 React 的渲染流程从异步、可中断变为同步、不可中断。这主要通过修改 react-reconciler 的宿主配置(HostConfig)和确保使用旧版 ReactDOM.render 入口(或类似其行为的自定义入口)来实现。
步骤 1: 避免 ReactDOM.createRoot
ReactDOM.createRoot 是 React 18 引入的并发模式入口。如果你的项目仍然使用 ReactDOM.render(React 17 及以前版本的主要入口),那么你已经避免了大部分并发模式的开销。对于自定义渲染器,你需要确保你的入口函数行为类似 ReactDOM.render,即触发一个同步的、非并发的更新。
// 避免使用此模式,因为它会激活并发特性
// const root = ReactDOM.createRoot(container);
// root.render(<App />);
// 推荐使用类似此模式的行为
// ReactDOM.render(<App />, container);
// 或者你的自定义渲染器入口
// customRenderer.render(<App />, container);
步骤 2: 自定义 react-reconciler 的调度器行为
react-reconciler 是 React 的核心协调算法,它与平台无关。它通过 HostConfig 对象与宿主环境进行交互。调度行为通过 HostConfig 中的 scheduleTimeout, cancelTimeout, noTimeout 以及 scheduleSyncCallback, scheduleCallback(这些通常由 react-dom 内部包装)等方法来控制。
要禁用并发模式,我们需要提供一个“同步”的调度器实现。这意味着任何需要调度的任务都应该立即执行,而不是推入异步队列。
下面是一个概念性的 HostConfig 片段,展示如何提供一个同步的调度器:
// 假设这是你的自定义渲染器的 HostConfig
const HostConfig = {
// ... 其他 HostConfig 属性 ...
// 时间调度相关,用于模拟 setTimeout/clearTimeout
// 在同步模式下,我们可以简单地立即执行回调
scheduleTimeout(fn, delay) {
// 理论上,delay 应该被忽略,或者如果需要最小的延迟,
// 可以用一个简单的 setTimeout(fn, 0) 来模拟微任务
// 但为了极致同步,我们可以直接调用
fn();
return -1; // 返回一个无效的 ID
},
cancelTimeout(id) {
// 无需取消,因为已经同步执行
},
noTimeout: -1, // 表示没有超时 ID
// 这里的 scheduleSyncCallback 和 scheduleCallback 是 react-reconciler 内部用来
// 处理优先级和并发更新的。在我们的同步模式下,它们也应该立即执行。
// 注意:这些方法通常不是直接在 HostConfig 中定义的,而是由 react-dom 封装后提供给 reconciler。
// 但如果我们完全自定义渲染器,我们可以在更高层级控制。
// 如果我们直接使用 react-reconciler,我们可以通过提供一个同步的 Scheduler 模块来达到目的。
// 模拟一个极其简化的同步调度器模块
// 实际情况中,你需要替换 react-dom 内部使用的 Scheduler 包
// 或者在你的 custom-renderer 中直接实现同步逻辑。
// 以下是概念性代码,并非直接的 HostConfig 属性
// --- 模拟 Scheduler ---
// import * as ReactScheduler from 'scheduler'; // 通常是这个
// 为了裁剪,我们需要一个自定义的 Scheduler 替代品
// 这是一个非常简化的同步 Scheduler 概念
// 它会直接执行任务,不进行时间切片
// 在实际的裁剪中,你可能需要一个空的或极简的 'scheduler' 包,
// 并且确保 react-reconciler 不会尝试使用其并发特性。
// 假设你的自定义渲染器入口点
// const ReactReconciler = require('react-reconciler');
// const customReconciler = ReactReconciler(HostConfig);
// 在更深层次,React 内部会调用 HostConfig.scheduleDeferredCallback 或
// 类似的抽象方法。我们需要确保这些方法不会引入异步调度。
// 关键在于 `react-reconciler` 的 `scheduleWork` 或者 `scheduleUpdateOnFiber`
// 最终如何触发真实 DOM 更新。在同步模式下,它应该立即同步地完成整个协调和提交阶段。
// 这通常涉及到在 `performSyncWorkOnRoot` 或类似函数中直接完成所有工作。
};
// 如何替换 Scheduler 包?
// 在 webpack/rollup 配置中,可以使用 alias 将 'scheduler' 指向一个空文件或一个同步实现。
// webpack.config.js
/*
module.exports = {
// ...
resolve: {
alias: {
'scheduler': path.resolve(__dirname, 'src/no-op-scheduler.js'),
'react-dom': path.resolve(__dirname, 'src/custom-react-dom.js'), // 可能也需要自定义
},
},
// ...
};
*/
// src/no-op-scheduler.js
// 这是一个极简的同步调度器替代品
module.exports = {
unstable_scheduleCallback: function(priority, callback) {
callback(); // 立即执行
return {}; // 返回一个空的句柄
},
unstable_cancelCallback: function(callbackID) {},
unstable_shouldYield: function() { return false; }, // 永远不让出控制权
unstable_getCurrentPriorityLevel: function() { return 0; }, // 总是返回最低优先级
unstable_runWithPriority: function(priority, callback) { return callback(); },
// ... 其他 unstable_ 方法也需要同步实现或空实现
};
通过这种方式,我们强制 react-reconciler 在发现有更新时,立即执行整个渲染周期,而不进行任何中断或时间切片。
内存影响:
移除并发模式意味着:
- 不再需要维护多个 Virtual DOM 树,只需当前树和工作中的树(在提交前合并)。
- 调度器相关的复杂数据结构(优先级队列、任务对象)将被移除或极大简化。
- 整体运行时内存占用将显著降低,因为不再需要为未提交的或暂停的渲染任务分配额外空间。
3. 深入剖析与裁剪:事件系统 (Synthetic Event System)
React 的合成事件系统是为了解决浏览器之间事件行为差异、提供统一的事件接口以及优化性能而设计的。它通过事件委托(Event Delegation)和事件池(Event Pooling)等机制来实现这些目标。
3.1 合成事件系统的工作原理及其开销
- 事件委托: React 不会直接将事件监听器附加到每个 DOM 元素上。相反,它只在应用程序的根节点(通常是
document或body)上附加一个事件监听器。当事件发生时,它会冒泡到根节点,然后 React 根据事件的target属性模拟事件的捕获和冒泡阶段,并将其分派给相应的 React 组件。 - 合成事件对象: React 不直接传递原生的浏览器事件对象。它创建了自己的
SyntheticEvent对象,这是一个跨浏览器兼容的封装器,提供了统一的事件接口。 - 事件池: 为了避免频繁创建和销毁
SyntheticEvent对象带来的性能开销,React 维护了一个事件对象池。当一个SyntheticEvent被使用后,它会被重置并返回到池中,供下次使用。 - 事件插件系统: React 内部有一个事件插件注册表 (
EventPluginRegistry),用于处理不同类型的事件(如鼠标事件、键盘事件、触摸事件等)。
内存和 CPU 开销:
EventPluginRegistry和插件: 维护事件类型到处理逻辑的映射,以及各种事件插件本身,都需要内存。SyntheticEvent对象池: 即使是池化,也需要预先分配一定数量的SyntheticEvent对象,并维护池的状态。- 事件分发逻辑: 从根节点捕获事件,然后模拟冒泡/捕获,查找正确的 React 监听器,创建/复用
SyntheticEvent对象,并最终调用组件中的处理器,这一整个过程涉及到大量的查找、对象创建和函数调用,增加了 CPU 负担。 - 额外的闭包和函数: 为了实现事件委托和分发,React 内部需要生成和维护额外的函数和闭包,以在事件触发时正确地绑定
this和传递参数。
3.2 为何在嵌入式设备上移除合成事件系统?
在嵌入式设备上,合成事件系统带来的复杂性和开销是不可接受的:
- 无浏览器兼容性需求: 嵌入式设备通常运行在单一、可控的渲染环境(如自定义的 UI 库、Canvas 渲染器或某种轻量级 DOM 模拟)。不存在跨浏览器兼容性的问题。
- 简化事件处理: 直接使用宿主环境的原生事件机制通常更简单、更高效。
- 内存节省: 移除
EventPluginRegistry、SyntheticEvent对象池和复杂的事件分发逻辑,可以显著减少内存占用。 - 性能提升: 避免了事件委托的额外处理层,事件可以直接传递给目标元素上的监听器,减少了事件处理的延迟。
- 自定义硬件事件: 嵌入式设备经常有独特的硬件事件(按钮按下、传感器数据变化),这些事件通常需要直接的硬件中断或轮询处理,React 的合成事件系统对此无能为力,反而会成为额外的负担。
3.3 裁剪合成事件系统的实践
裁剪合成事件系统意味着我们需要让 react-reconciler 直接将事件监听器附加到实际的渲染节点上,并直接传递宿主环境的原生事件对象。
步骤 1: 修改 HostConfig 以直接附加事件监听器
在 HostConfig 中,有几个关键方法负责处理元素的属性(props),包括事件监听器。我们需要修改 createInstance 和 prepareUpdate 方法,让它们识别 on* 属性并直接将其作为事件监听器附加到宿主实例上。
// 假设这是你的自定义渲染器的 HostConfig
const HostConfig = {
// ... 其他 HostConfig 属性 ...
// 创建一个宿主实例(例如,一个自定义的UI元素对象)
createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
// 假设我们有一个简单的 UI 元素工厂
const instance = createUIElement(type);
// 在这里直接处理事件监听器
for (const propKey in props) {
if (propKey.startsWith('on') && typeof props[propKey] === 'function') {
const eventName = propKey.toLowerCase().substring(2); // 例如 'onClick' -> 'click'
// 假设你的 UI 元素有一个 addEventListener 方法
instance.addEventListener(eventName, props[propKey]);
} else {
// 其他属性处理
instance.setProperty(propKey, props[propKey]);
}
}
return instance;
},
// 更新宿主实例的属性
prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, hostContext) {
const updatePayload = []; // 用于存储需要更新的属性
for (const propKey in oldProps) {
if (newProps.hasOwnProperty(propKey) && oldProps[propKey] !== newProps[propKey]) {
if (propKey.startsWith('on') && typeof newProps[propKey] === 'function') {
// 事件处理器发生了变化,需要移除旧的,添加新的
const eventName = propKey.toLowerCase().substring(2);
instance.removeEventListener(eventName, oldProps[propKey]);
instance.addEventListener(eventName, newProps[propKey]);
} else if (!propKey.startsWith('on')) { // 忽略事件处理器,因为已经在上面处理
updatePayload.push(propKey, newProps[propKey]);
}
} else if (!newProps.hasOwnProperty(propKey) && propKey.startsWith('on')) {
// 事件处理器被移除
const eventName = propKey.toLowerCase().substring(2);
instance.removeEventListener(eventName, oldProps[propKey]);
}
}
for (const propKey in newProps) {
if (!oldProps.hasOwnProperty(propKey)) {
if (propKey.startsWith('on') && typeof newProps[propKey] === 'function') {
// 新增事件处理器
const eventName = propKey.toLowerCase().substring(2);
instance.addEventListener(eventName, newProps[propKey]);
} else if (!propKey.startsWith('on')) {
updatePayload.push(propKey, newProps[propKey]);
}
}
}
return updatePayload.length > 0 ? updatePayload : null;
},
// 提交更新,这里根据 prepareUpdate 返回的 payload 来更新实例
commitUpdate(instance, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
for (let i = 0; i < updatePayload.length; i += 2) {
instance.setProperty(updatePayload[i], updatePayload[i + 1]);
}
},
// ... 其他 HostConfig 属性 ...
};
// 假设的 UI 元素工厂和事件处理方法
function createUIElement(type) {
// 实际中可能是一个渲染到 Canvas 的对象,或者一个硬件抽象
console.log(`Creating UI Element of type: ${type}`);
const listeners = {};
return {
type,
props: {},
addEventListener(eventName, handler) {
if (!listeners[eventName]) listeners[eventName] = [];
listeners[eventName].push(handler);
console.log(`Added listener for ${eventName} on ${type}`);
},
removeEventListener(eventName, handler) {
if (listeners[eventName]) {
listeners[eventName] = listeners[eventName].filter(h => h !== handler);
console.log(`Removed listener for ${eventName} on ${type}`);
}
},
setProperty(key, value) {
this.props[key] = value;
console.log(`Set property ${key} = ${value} on ${type}`);
},
// 模拟事件触发,直接调用注册的处理器,传递原生事件对象
dispatchNativeEvent(eventName, nativeEvent) {
if (listeners[eventName]) {
listeners[eventName].forEach(handler => handler(nativeEvent));
}
}
};
}
在上述 createInstance 和 prepareUpdate 中,我们直接检查 propKey.startsWith('on'),如果发现是事件监听器,就直接调用宿主实例(instance)上的 addEventListener 或 removeEventListener 方法。这意味着:
- 没有事件委托: 事件监听器直接附加到目标元素上,而非根节点。
- 没有合成事件对象: 我们直接将宿主环境的原生事件对象传递给组件的事件处理函数。
- 没有事件池: 因为我们不创建
SyntheticEvent对象,所以也不需要池化。
步骤 2: 移除对 react-dom/client 或 react-dom/server 等包的依赖
这些包内部包含了完整的合成事件系统实现。通过构建自定义渲染器并直接使用 react-reconciler,我们可以完全绕过这些。
内存影响:
移除合成事件系统意味着:
- 不再需要
EventPluginRegistry及其内部数据结构。 - 不再需要
SyntheticEvent对象及其事件池。 - 事件委托的内部逻辑和数据结构也会被移除。
- 整体运行时内存占用会显著降低,尤其是在有大量交互元素时。
4. 定制 react-reconciler:构建最小化运行时
react-reconciler 是 React 的核心所在,它是一个与平台无关的渲染器核心。它提供了协调算法,通过 HostConfig 与具体的宿主环境(如 DOM、Canvas、命令行等)进行交互。对于嵌入式设备,这是我们实现极致裁剪的关键接口。
4.1 HostConfig 的核心作用
HostConfig 是一个包含一系列方法的 JavaScript 对象,这些方法定义了 react-reconciler 如何与你的特定渲染目标进行交互。通过实现这些方法,你可以告诉 React 如何:
- 创建、更新和删除宿主实例(你的 UI 元素)。
- 处理文本内容。
- 在宿主实例之间插入、移动和附加子节点。
- 处理属性(包括事件监听器,如前所述)。
- 处理调度和定时器(如前所述)。
- 管理上下文。
下表列出了一些关键的 HostConfig 方法及其在裁剪中的考量:
| 方法名称 | 描述 | 裁剪考量 |
|---|---|---|
createInstance |
创建宿主实例(如 <div /> 对应的 DOM 元素) |
简化实例创建逻辑,直接根据 type 创建自定义 UI 元素。在这里处理属性和事件监听器,避免 React 内部的复杂事件系统。 |
createTextInstance |
创建文本节点实例 | 简化文本节点创建,如果你的 UI 不支持富文本,可能只需要一个简单的字符串或渲染对象。 |
appendInitialChild |
将子节点添加到新创建的父节点 | 简单地将子元素添加到父元素的子节点列表中。 |
appendChildToContainer |
将子节点添加到根容器 | 类似于 appendInitialChild,但针对根容器。 |
prepareUpdate |
准备更新,返回一个更新 payload | 比较旧的 props 和新的 props,生成一个更新指令列表。在这里处理事件监听器的变化(移除旧的,添加新的)。避免生成复杂的更新对象。 |
commitUpdate |
提交更新,将 payload 应用到宿主实例 | 根据 prepareUpdate 返回的 payload,同步地更新宿主实例的属性。 |
removeChild |
移除子节点 | 简单地从父元素的子节点列表中移除。 |
insertBefore |
在指定子节点之前插入子节点 | 简单地在子节点列表中插入。 |
scheduleTimeout |
调度超时任务 | 关键裁剪点: 实现为同步调用 fn() 或一个非常轻量级的 setTimeout(fn, 0),确保不引入异步调度机制。 |
cancelTimeout |
取消超时任务 | 如果 scheduleTimeout 是同步的,则此方法可为空。 |
noTimeout |
表示没有超时 ID | 返回一个常量,如 -1。 |
shouldSetTextContent |
判断是否应该直接设置文本内容 | 根据你的 UI 元素是否支持直接设置文本内容来返回 true 或 false。 |
supportsMutation |
是否支持突变操作(DOM 操作) | 返回 true。 |
supportsPersistence |
是否支持持久化(如服务器端渲染) | 返回 false。 |
supportsHydration |
是否支持水合(客户端接管服务器端渲染) | 返回 false。 |
4.2 构建一个极简的自定义渲染器
构建自定义渲染器涉及到以下几个步骤:
- 导入
react-reconciler: 这是核心。 - 定义
HostConfig: 实现上述方法,使其与你的嵌入式 UI 框架或自定义渲染 API 交互。 - 创建渲染器实例: 调用
ReactReconciler(HostConfig)。 - 暴露渲染方法: 创建一个类似
ReactDOM.render的公共 API,作为你的渲染器入口。
// custom-embedded-renderer/index.js
import * as Reconciler from 'react-reconciler';
import {
createUIElement, // 假设这是你的嵌入式UI库提供的创建元素的方法
updateUIElementProperty,
appendUIChild,
removeUIChild,
insertUIChild,
setTextContent,
// ... 其他与宿主环境交互的方法
} from './embedded-ui-host-api'; // 你的嵌入式UI库的接口
// 你的宿主配置
const HostConfig = {
// 调度相关 - 确保同步执行,禁用并发
scheduleTimeout: (fn, delay) => { fn(); return -1; },
cancelTimeout: (id) => {},
noTimeout: -1,
// 实例创建
createInstance: (type, props, rootContainerInstance, hostContext, internalInstanceHandle) => {
const instance = createUIElement(type);
for (const propKey in props) {
if (propKey.startsWith('on') && typeof props[propKey] === 'function') {
const eventName = propKey.toLowerCase().substring(2);
instance.addEventListener(eventName, props[propKey]); // 直接附加原生事件
} else if (propKey !== 'children') { // 忽略children,React会单独处理
updateUIElementProperty(instance, propKey, props[propKey]);
}
}
return instance;
},
createTextInstance: (text, rootContainerInstance, hostContext, internalInstanceHandle) => {
return setTextContent(text); // 你的UI库创建文本节点的方法
},
// 更新
prepareUpdate: (instance, type, oldProps, newProps, rootContainerInstance, hostContext) => {
const updatePayload = [];
for (const propKey in oldProps) {
if (propKey !== 'children' && newProps.hasOwnProperty(propKey) && oldProps[propKey] !== newProps[propKey]) {
if (propKey.startsWith('on') && typeof newProps[propKey] === 'function') {
const eventName = propKey.toLowerCase().substring(2);
instance.removeEventListener(eventName, oldProps[propKey]);
instance.addEventListener(eventName, newProps[propKey]);
} else {
updatePayload.push(propKey, newProps[propKey]);
}
} else if (propKey !== 'children' && !newProps.hasOwnProperty(propKey) && propKey.startsWith('on')) {
const eventName = propKey.toLowerCase().substring(2);
instance.removeEventListener(eventName, oldProps[propKey]);
}
}
for (const propKey in newProps) {
if (propKey !== 'children' && !oldProps.hasOwnProperty(propKey)) {
if (propKey.startsWith('on') && typeof newProps[propKey] === 'function') {
const eventName = propKey.toLowerCase().substring(2);
instance.addEventListener(eventName, newProps[propKey]);
} else {
updatePayload.push(propKey, newProps[propKey]);
}
}
}
return updatePayload.length > 0 ? updatePayload : null;
},
commitUpdate: (instance, updatePayload, type, oldProps, newProps, internalInstanceHandle) => {
for (let i = 0; i < updatePayload.length; i += 2) {
updateUIElementProperty(instance, updatePayload[i], updatePayload[i + 1]);
}
},
// 节点操作
appendInitialChild: (parentInstance, child) => appendUIChild(parentInstance, child),
appendChildToContainer: (container, child) => appendUIChild(container, child),
removeChild: (parentInstance, child) => removeUIChild(parentInstance, child),
insertBefore: (parentInstance, child, beforeChild) => insertUIChild(parentInstance, child, beforeChild),
// 文本内容
shouldSetTextContent: (type, props) => typeof props.children === 'string' || typeof props.children === 'number',
setTextContent: (instance, text) => setTextContent(instance, text),
// 其他
getRootHostContext: (rootContainerInstance) => ({}),
getChildHostContext: (parentHostContext, type, rootContainerInstance) => parentHostContext,
getPublicInstance: (instance) => instance,
prepareForCommit: (containerInfo) => null,
resetAfterCommit: (containerInfo) => {},
finalizeInitialChildren: (instance, type, props, rootContainerInstance, hostContext) => false,
getDiffForTextInstance: (textInstance, oldText, newText) => oldText !== newText,
shouldDeprioritizeSubtree: (type, props) => false,
scheduleHydration: (callback) => {}, // 不支持水合
// 特性支持
supportsMutation: true,
supportsPersistence: false,
supportsHydration: false,
is