自己造一個 ReactDOM
React
可以看作是三部分的組合:
-
scheduler
,調度器,用於調度任務 -
reconciler
,協調器,用於計算任務造成的副作用 -
renderer
,渲染器,用於在宿主環境執行副作用
這三者都是獨立的包,我們項目裏引入的ReactDOM
可以看作是以下三部分代碼打包而成:
-
scheduler
的主要邏輯 -
reconciler
部分邏輯 -
ReactDOM renderer
的主要邏輯
本文會教你如何基於官方的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.appendChild
、element.removeChild
。如果是Native
環境則不是這種工作方式。
接下來我們來實現這些API
。
實現 API
這些API
可以分爲如下幾類。
初始化環境信息
getRootHostContext
與getChildHostContext
用於初始化上下文信息。
生成 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 地址 [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