解析 React 在低功耗嵌入式设备上的裁剪:如何去掉并发模式和事件系统以节省内存?

各位同仁,各位技术爱好者,大家好!

今天,我们来探讨一个看似激进,实则充满潜力的主题:如何在资源受限、功耗敏感的嵌入式设备上运行 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 包,它利用浏览器提供的 MessageChannelrequestAnimationFrame 来实现宏任务和微任务的调度,从而实现时间切片。它维护一个优先级队列,根据任务的优先级来决定何时执行、何时暂停。
  • 双缓冲树 (Double Buffering): 为了实现可中断渲染,React 需要维护多棵 Virtual DOM 树。在并发模式下,除了当前的 "work-in-progress" 树(正在构建的树)和 "current" 树(当前屏幕上显示的树)之外,可能还有其他中间状态的树或任务相关的元数据。这意味着更多的内存占用。
  • 任务管理和优先级: React 内部需要复杂的逻辑来管理不同优先级的更新任务,包括区分紧急更新(如用户输入)和非紧急更新(如数据获取)。这涉及到任务队列、过期时间计算以及任务状态的追踪。
  • 副作用列表和链表: 在协调过程中,React 会构建一个副作用列表(Effect List),其中包含需要对实际 DOM 进行操作的所有变更(插入、更新、删除)。在并发模式下,这些列表的管理更为复杂,以支持中断和恢复。

内存和 CPU 开销:

  • 额外的 Virtual DOM 树: 双缓冲和其他中间状态的维护会显著增加内存占用。
  • 调度器数据结构: 优先级队列、任务对象、定时器等都需要内存。
  • 复杂的控制流: 任务的中断、恢复、优先级切换、依赖跟踪等逻辑会增加 CPU 负担。
  • 浏览器 API 依赖:MessageChannelrequestAnimationFrame 的依赖意味着需要提供相应的 polyfill 或模拟,这本身就是额外的代码和潜在的运行时开销。

2.2 为何在嵌入式设备上移除并发模式?

在低功耗嵌入式设备上,并发模式带来的好处往往微不足道,甚至弊大于利:

  • 简单 UI,无复杂交互: 嵌入式设备的 UI 通常功能专一,交互模式相对简单,很少有需要复杂时间切片来避免卡顿的场景。
  • 内存是王道: 额外的 Virtual DOM 树和调度器数据结构会迅速耗尽有限的内存。
  • 同步更新更可控: 在资源受限的环境中,完全同步的、阻塞式的更新模型反而更容易理解和控制,开发者可以精确地知道何时发生渲染,何时释放 CPU。
  • 无浏览器环境: 缺乏 MessageChannelrequestAnimationFrame 等浏览器原生 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 元素上。相反,它只在应用程序的根节点(通常是 documentbody)上附加一个事件监听器。当事件发生时,它会冒泡到根节点,然后 React 根据事件的 target 属性模拟事件的捕获和冒泡阶段,并将其分派给相应的 React 组件。
  • 合成事件对象: React 不直接传递原生的浏览器事件对象。它创建了自己的 SyntheticEvent 对象,这是一个跨浏览器兼容的封装器,提供了统一的事件接口。
  • 事件池: 为了避免频繁创建和销毁 SyntheticEvent 对象带来的性能开销,React 维护了一个事件对象池。当一个 SyntheticEvent 被使用后,它会被重置并返回到池中,供下次使用。
  • 事件插件系统: React 内部有一个事件插件注册表 (EventPluginRegistry),用于处理不同类型的事件(如鼠标事件、键盘事件、触摸事件等)。

内存和 CPU 开销:

  • EventPluginRegistry 和插件: 维护事件类型到处理逻辑的映射,以及各种事件插件本身,都需要内存。
  • SyntheticEvent 对象池: 即使是池化,也需要预先分配一定数量的 SyntheticEvent 对象,并维护池的状态。
  • 事件分发逻辑: 从根节点捕获事件,然后模拟冒泡/捕获,查找正确的 React 监听器,创建/复用 SyntheticEvent 对象,并最终调用组件中的处理器,这一整个过程涉及到大量的查找、对象创建和函数调用,增加了 CPU 负担。
  • 额外的闭包和函数: 为了实现事件委托和分发,React 内部需要生成和维护额外的函数和闭包,以在事件触发时正确地绑定 this 和传递参数。

3.2 为何在嵌入式设备上移除合成事件系统?

在嵌入式设备上,合成事件系统带来的复杂性和开销是不可接受的:

  • 无浏览器兼容性需求: 嵌入式设备通常运行在单一、可控的渲染环境(如自定义的 UI 库、Canvas 渲染器或某种轻量级 DOM 模拟)。不存在跨浏览器兼容性的问题。
  • 简化事件处理: 直接使用宿主环境的原生事件机制通常更简单、更高效。
  • 内存节省: 移除 EventPluginRegistrySyntheticEvent 对象池和复杂的事件分发逻辑,可以显著减少内存占用。
  • 性能提升: 避免了事件委托的额外处理层,事件可以直接传递给目标元素上的监听器,减少了事件处理的延迟。
  • 自定义硬件事件: 嵌入式设备经常有独特的硬件事件(按钮按下、传感器数据变化),这些事件通常需要直接的硬件中断或轮询处理,React 的合成事件系统对此无能为力,反而会成为额外的负担。

3.3 裁剪合成事件系统的实践

裁剪合成事件系统意味着我们需要让 react-reconciler 直接将事件监听器附加到实际的渲染节点上,并直接传递宿主环境的原生事件对象。

步骤 1: 修改 HostConfig 以直接附加事件监听器

HostConfig 中,有几个关键方法负责处理元素的属性(props),包括事件监听器。我们需要修改 createInstanceprepareUpdate 方法,让它们识别 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));
      }
    }
  };
}

在上述 createInstanceprepareUpdate 中,我们直接检查 propKey.startsWith('on'),如果发现是事件监听器,就直接调用宿主实例(instance)上的 addEventListenerremoveEventListener 方法。这意味着:

  1. 没有事件委托: 事件监听器直接附加到目标元素上,而非根节点。
  2. 没有合成事件对象: 我们直接将宿主环境的原生事件对象传递给组件的事件处理函数。
  3. 没有事件池: 因为我们不创建 SyntheticEvent 对象,所以也不需要池化。

步骤 2: 移除对 react-dom/clientreact-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 元素是否支持直接设置文本内容来返回 truefalse
supportsMutation 是否支持突变操作(DOM 操作) 返回 true
supportsPersistence 是否支持持久化(如服务器端渲染) 返回 false
supportsHydration 是否支持水合(客户端接管服务器端渲染) 返回 false

4.2 构建一个极简的自定义渲染器

构建自定义渲染器涉及到以下几个步骤:

  1. 导入 react-reconciler 这是核心。
  2. 定义 HostConfig 实现上述方法,使其与你的嵌入式 UI 框架或自定义渲染 API 交互。
  3. 创建渲染器实例: 调用 ReactReconciler(HostConfig)
  4. 暴露渲染方法: 创建一个类似 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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注