1

自己造一个ReactDOM

 2 years ago
source link: https://segmentfault.com/a/1190000040999929
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

大家好,我卡颂。

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

欢迎加入人类高质量前端框架群,带飞


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK