自己造一個 ReactDOM

React可以看作是三部分的組合:

這三者都是獨立的包,我們項目裏引入的ReactDOM可以看作是以下三部分代碼打包而成:

本文會教你如何基於官方的reconciler,實現迷你ReactDOM

本文參考 Hello World Custom React Renderer[1]

項目初始化

通過CRA建立項目(或用已有項目):

create-react-app xxx

新建customRenderer.js,引入react-reconciler並完成初始化:

// 本文使用的reconciler版本是0.26.2
import ReactReconciler from 'react-reconciler';

const hostConfig = {};
const ReactReconcilerInst = ReactReconciler(hostConfig);

其中hostConfig就是宿主環境的配置項。

最後,customRenderer.js導出一個包含render方法的對象:

export default {
  render: (reactElement, domElement, callback) ={
    // 創建根節點
    if (!domElement._rootContainer) {
      domElement._rootContainer = ReactReconcilerInst.createContainer(domElement, false);
    }

    return ReactReconcilerInst.updateContainer(reactElement, domElement._rootContainer, null, callback);
  }
};

在項目入口文件,將ReactDOM換成我們實現的CustomRenderer

import ReactDOM from 'react-dom';
import CustomRenderer from './customRenderer';

// 替換ReactDOM
CustomRenderer.render(
  <App />,
  document.getElementById('root')
);

實現 ReactDOM

接下來我們實現hostConfig配置,首先填充空函數避免應用報錯:

const hostConfig = {
  supportsMutation: true,
  getRootHostContext() {},
  getChildHostContext() {},
  prepareForCommit() {},
  resetAfterCommit() {},
  shouldSetTextContent() {},
  createInstance() {},
  createTextInstance() {},
  appendInitialChild() {},
  finalizeInitialChildren() {},
  clearContainer() {},
  appendInitialChild() {},
  appendChild() {},
  appendChildToContainer() {},
  prepareUpdate() {},
  commitUpdate() {},
  commitTextUpdate() {},
  removeChild() {}
}

注意這裏唯一一個Boolean類型的配置項supportsMutation,他表示宿主環境的API支持mutation

這是DOM API的工作方式,比如element.appendChildelement.removeChild。如果是Native環境則不是這種工作方式。

接下來我們來實現這些API

實現 API

這些API可以分爲如下幾類。

初始化環境信息

getRootHostContextgetChildHostContext用於初始化上下文信息。

生成 DOM 節點

可以將createTextInstance實現如下:

createTextInstance: (text) ={
  return document.createTextNode(text);
}

關鍵邏輯的判斷

shouldSetTextContent用於判斷組件的children是否是文本節點,實現如下:

shouldSetTextContent: (_, props) ={
    return typeof props.children === 'string' || typeof props.children === 'number';
},

DOM 操作

appendInitialChild用於插入DOM節點,實現如下:

appendInitialChild: (parent, child) ={
  parent.appendChild(child);
},

commitTextUpdate用於改變文本節點,實現如下:

commitTextUpdate(textInstance, oldText, newText) {
  textInstance.text = newText;
},

removeChild用於刪除子節點,實現如下:

removeChild(parentInstance, child) {
  parentInstance.removeChild(child);
}

當實現了所有API後,頁面就能正常渲染了:

完整實現的Demo地址見:完整 Demo 地址 [2]

總結

經過本文的學習,我們實現了一個簡易ReactDOM

如果你想在任何可以繪製UI的環境使用React,都可以利用react-reconciler實現該環境下的React

比如,Introduction To React Native Renderers[3] 教你如何在Native環境實現React

參考資料

[1]

Hello World Custom React Renderer: https://agent-hunt.medium.com/hello-world-custom-react-renderer-9a95b7cd04bc

[2]

完整 Demo 地址: https://codesandbox.io/s/quiet-feather-05gvk?file=/src/index.js

[3]

Introduction To React Native Renderers: https://agent-hunt.medium.com/introduction-to-react-native-renderers-aka-react-native-is-the-java-and-react-native-renderers-are-828a0022f433

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/t5JM2ABReTdlKFFkG-Isow