自己造一個ReactDOM

卡頌發表於2021-11-25

大家好,我卡頌。

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

  • scheduler,排程器,用於排程任務
  • reconciler,協調器,用於計算任務造成的副作用
  • renderer,渲染器,用於在宿主環境執行副作用

這三者都是獨立的包,我們專案裡引入的ReactDOM可以看作是以下三部分程式碼打包而成:

  • scheduler的主要邏輯
  • reconciler部分邏輯
  • ReactDOM renderer的主要邏輯

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

本文參考Hello World Custom React Renderer

專案初始化

通過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節點

  • createInstance用於建立DOM節點
  • createTextInstance用於建立文字節點

可以將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地址

總結

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

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

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

歡迎加入人類高質量前端框架群,帶飛

相關文章