大家好,我卡颂。
React可以看作是三部分的组合:
- scheduler,调度器,用于调度任务
- reconciler,协调器,用于计算任务造成的副作用
- renderer,渲染器,用于在宿主环境执行副作用
这三者都是独立的包,我们项目里引入的ReactDOM可以看作是以下三部分代码打包而成:
- scheduler的主要逻辑
- reconciler部分逻辑
- ReactDOM renderer的主要逻辑
本文会教你如何基于官方的reconciler,实现迷你ReactDOM。
本文参考Hello World Custom React Renderer[1]
项目初始化
通过CRA建立项目(或用已有项目):
create-react-app xxx
- 1.
新建customRenderer.js,引入react-reconciler并完成初始化:
// 本文使用的reconciler版本是0.26.2
import ReactReconciler from 'react-reconciler';
const hostConfig = {};
const ReactReconcilerInst = ReactReconciler(hostConfig);
- 1.
- 2.
- 3.
- 4.
- 5.
其中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);
}
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
在项目入口文件,将ReactDOM换成我们实现的CustomRenderer:
import ReactDOM from 'react-dom';
import CustomRenderer from './customRenderer';
// 替换ReactDOM
CustomRenderer.render(
<App />,
document.getElementById('root')
);
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
实现ReactDOM接下来我们实现hostConfig配置,首先填充空函数避免应用报错:
const hostConfig = {
supportsMutation: true,
getRootHostContext() {},
getChildHostContext() {},
prepareForCommit() {},
resetAfterCommit() {},
shouldSetTextContent() {},
createInstance() {},
createTextInstance() {},
appendInitialChild() {},
finalizeInitialChildren() {},
clearContainer() {},
appendInitialChild() {},
appendChild() {},
appendChildToContainer() {},
prepareUpdate() {},
commitUpdate() {},
commitTextUpdate() {},
removeChild() {}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
注意这里唯一一个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);
}
- 1.
- 2.
- 3.
关键逻辑的判断
shouldSetTextContent用于判断组件的children是否是文本节点,实现如下:
shouldSetTextContent: (_, props) => {
return typeof props.children === 'string' || typeof props.children === 'number';
},
- 1.
- 2.
- 3.
DOM操作
appendInitialChild用于插入DOM节点,实现如下:
appendInitialChild: (parent, child) => {
parent.appendChild(child);
},
- 1.
- 2.
- 3.
commitTextUpdate用于改变文本节点,实现如下:
commitTextUpdate(textInstance, oldText, newText) {
textInstance.text = newText;
},
- 1.
- 2.
- 3.
removeChild用于删除子节点,实现如下:
removeChild(parentInstance, child) {
parentInstance.removeChild(child);
}
- 1.
- 2.
- 3.
当实现了所有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