如何从头自定义react renderer-实现hostConfig(2/3)

525 阅读3分钟

英文

  1. ReCap 回顾
  2. as compared to 相比于

文章

HostConfig

我们必须在HostConfig中实现所有必须的平台特定函数,这些函数有很多,但是我们不用全部实现,在初始化渲染的时候的reconciler会调用hostConfig中不同的函数。

Initial render

根据报错,先补全缺失的函数,确保intial render可以通过

const HostConfig = {
  supportsMutation: true,
  getRootHostContext: function (...args) {
    console.log("getRootHostContext", ...args);
  },
  getChildHostContext: function (...args) {
    console.log("getChildHostContext", ...args);
  },
  shouldSetTextContent: function (...args) {
    console.log("shouldSetTextContent", ...args);
  },
  createTextInstance: function (...args) {
    console.log("createTextInstance", ...args);
  },
  createInstance: function (...args) {
    console.log("createInstance", ...args);
  },
  appendInitialChild: function (...args) {
    console.log("appendInitialChild", ...args);
  },
  finalizeInitialChildren: function (...args) {
    console.log("finalizeInitialChildren", ...args);
  },
  prepareForCommit: function (...args) {
    console.log("prepareForCommit", ...args);
  },
  resetAfterCommit: function (...args) {
    console.log("resetAfterCommit", ...args);
  },
  detachDeletedInstance: function (...args) {
    console.log("detachDeletedInstance", ...args);
  },
  clearContainer: function (...args) {
    console.log("clearContainer", ...args);
  },
  removeChildFromContainer: function (...args) {
    console.log("removeChildFromContainer", ...args);
  },
  appendChildToContainer: function (...args) {
    console.log("appendChildToContainer", ...args);
  },
};

按照博客的补全所有方法,还是有一个appendAllChild方法没有检测到,哪怕在HostConfig里补上还是会报这个问题,最后在reconciler文档中找到一个配置supportsMutation,在Dom中拥有appendChild和removeChild的方法,将其配置为true.链接 最后补全所有函数

const HostConfig = {
  supportsMutation: true,
  /**
   * 让你和其他函数分享一些context
   * @param {*} nextRootInstance 你定义的rootDom
   */
  getRootHostContext: function (nextRootInstance) {
    console.log("getRootHostContext", nextRootInstance);
    let rootContext = {};
    return rootContext;
  },
  /**
   *从父级节点访问上下文,给子级节点传递上下文
   * @param  {...any} parentContext 父级节点的上下文 fiberType: fiber的类型 rootInstance: rootDom
   */
  getChildHostContext: function (parentContext, fiberType, rootInstance) {
    console.log("getChildHostContext", parentContext, fiberType);
    let context = {};
    return context;
  },
  /**
   * 如果函数返回 true,则文本将在宿主元素内创建,并且不会单独创建新的文本元素。
   * 如果返回 true,则接下来调用的是当前元素的 createInstance方法, 并且遍历将在此节点处停止(不会遍历此元素的子元素)。
   * 如果返回 false,子元素将会继续调用getChildHostContext与shouldSetTextContent。它会一直持续到 shouldSetTextContent 返回 true 或者递归到树的最后一个node节点(通常是文本节点)。当它到达最后一个叶子节点时,它将调用 createTextInstance方法。
   * @param  {...any} type fiber的类型 props: 宿主元素的props
   */
  shouldSetTextContent: function (type, props) {
    console.log("shouldSetTextContent", type, props);
    return false;
  },
  /**
   * 这个函数用于创建文本节点
   * @param {*} newText 包含的文本内容
   * @param {*} rootContainerInstance 跟节点
   * @param {*} currentHostContext 宿主元素上下文
   * @param {*} workInProgress txt节点的fiber
   * @returns 一个文本节点
   */
  createTextInstance: function (
    newText,
    rootContainerInstance,
    currentHostContext,
    workInProgress
  ) {
    console.log(
      "createTextInstance",
      newText,
      rootContainerInstance,
      currentHostContext,
      workInProgress
    );
    return document.createTextNode(newText);
  },
  /**
   * createInstance被所有的host nodes调用(除了叶子文本节点)
   * @param {*} type
   * @param {*} newProps
   * @param {*} rootContainerInstance
   * @param {*} currentHostContext
   * @param {*} workInProgress
   * @returns 一个宿主元素
   */
  createInstance: function (
    type,
    newProps,
    rootContainerInstance,
    currentHostContext,
    workInProgress
  ) {
    console.log(
      "createInstance",
      type,
      newProps,
      rootContainerInstance,
      currentHostContext,
      workInProgress
    );
    const element = document.createElement(type);
    element.className = newProps.className || "";
    element.style = newProps.style;
    return element;
  },
  /**
   * 将子节点插入父节点
   * @param {*} parent
   * @param {*} child
   */
  appendInitialChild: function (parent, child) {
    console.log("appendInitialChild", parent, child);
    parent.appendChild(child);
  },
  /**
   * 在 react native 渲染器的情况下,这个函数除了返回 false 什么都不做。
   * 在 react-dom 的情况下,这会添加默认的 dom 属性,例如事件侦听器等。为了实现某些input元素的autofocus(autofocus只能在渲染完成后发生),react-dom 发送返回类型为 true
   * @param {*} instance appendInitialChild 之后的 dom 元素
   * @param {*} type
   * @param {*} newProps
   * @param {*} rootContainerInstance
   * @param {*} currentHostContext
   * @returns 决定是否需要调用该节点的commitMount
   */
  finalizeInitialChildren: function (
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext
  ) {
    console.log(
      "finalizeInitialChildren",
      instance,
      type,
      newProps,
      rootContainerInstance,
      currentHostContext
    );
    return newProps.autofocus;
  },
  /**
   * 完成制作内存渲染树时将会执行该函数
   * @param {*} rootContainerInstance
   */
  prepareForCommit: function (rootContainerInstance) {
    console.log("prepareForCommit", rootContainerInstance);
  },
  /**
   * 内存渲染树被插入到跟节点后执行该函数,这里可以重新启用prepareForCommit中禁用的事件
   * @param {*} rootContainerInstance
   */
  resetAfterCommit: function (rootContainerInstance) {
    console.log("resetAfterCommit", rootContainerInstance);
  },
  detachDeletedInstance: function (...args) {
    console.log("detachDeletedInstance", ...args);
  },
  clearContainer: function (container) {
    console.log("clearContainer", container);
    container.innerHtml = "";
  },
  removeChildFromContainer: function (parent, child) {
    console.log("removeChildFromContainer", parent, child);
    parent.removeChild(child);
  },
  /**
   * 将我们的内存树挂载到root div。但是这个函数只有在我们设置了 supportsMutation:true 时才有效。
   */
  appendChildToContainer: function (parent, child) {
    console.log("appendChildToContainer", parent, child);
    parent.appendChild(child);
  },
  commitMount: (domElement, type, newProps, fiberNode) => {
    domElement.focus();
  },
};

最后,通过我们自定义的渲染器渲染出了hello world,这里出现了一个问题,world的蓝色样式并没有加上,因为文章中作者在createInstance函数中element.style=style直接赋值,而jsx中react是对象,原生dom不识别这种写法,所以我们需要对style进行转换一下

const styleObj = newProps.style || {};
let str = "";
for (let key in styleObj) {
  str += `${key}:${styleObj[key]};`;
}
element.style = str;

参考

文章链接