Loading...
墨滴

早睡wqi

2021/09/12  阅读:26  主题:默认主题

React render源码解析

React render流程解析

学习react源码首先从react入口函数开始学习,这篇文章你将学习到如何进行调试代码和学会react render执行流程。

调试代码

这里只是进行简单的流程介绍,具体的可以看卡颂老师的拉取源码教程[1]

# 拉取代码
git clone https://github.com/facebook/react.git

# 执行打包命令
yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE

# 通过yarn link改变依赖包指向
cd build/node_modules/react
yarn link
cd build/node_modules/react-dom
yarn link

# 通过create-react-app创建应用后
yarn link react react-dom

render

render函数是执行react同步渲染的入口函数,在react18之前还是使用这个函数作为主入口。

render 接受三个参数,第一个参数是ReactElement,第二个参数为组件所要挂载的DOM节点,第三个参数为回调函数。

// 调用
ReactDOM.render(<App />document.getElementById("root"));
// 源码
function render(element, container, callback{
  // 判断是否是有效dom节点
  if (!isValidContainer(container)) {
    //省略
  }

  {
    // 是否已经是根节点
    var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;
    // 下面是抛出错误
  }
  // 挂载节点
  return legacyRenderSubtreeIntoContainer(null, element, //jsx根节点
  container, //root节点
  false, callback //回调
  );
}

有效节点[2]

var ELEMENT_NODE = 1;//元素节点
var TEXT_NODE = 3;//文本节点
var COMMENT_NODE = 8;//注释节点
var DOCUMENT_NODE = 9;//文档,DOM树根节点
var DOCUMENT_FRAGMENT_NODE = 11;//节点片段

legacyRenderSubtreeIntoContainer

react-dom\src\client\ReactDOMLegacy.js

function legacyRenderSubtreeIntoContainer(
    parentComponent: ? React$Component <anyany> ,// 父节点
    children : ReactNodeList,// <App />
    container: Container, // div#root
    forceHydrate: boolean//服务端渲染
    callback: ? Function// 回调
{
    // 开发环境标识__DEV__
    if (__DEV__) {
        // 判断根节点是否符合规则
        topLevelUpdateWarnings(container);
        // 存在回调函数的话,验证是否是函数类型
        warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
    }
    // _reactRootContainer指向根节点
    let root: RootType = (container._reactRootContainer: any);
    let fiberRoot;
    if (!root) {
        // Initial mount
        root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
            container,
            forceHydrate,
        );
        fiberRoot = root._internalRoot;
        if (typeof callback === 'function') {
            const originalCallback = callback;
            callback = function({
                const instance = getPublicRootInstance(fiberRoot);
                originalCallback.call(instance);
            };
        }
        // Initial mount should not be batched.
        // 进入scheduled阶段,render没有
        unbatchedUpdates(() => {
            updateContainer(children, fiberRoot, parentComponent, callback);
        });
    } else {
        // 存在root,更新dom使用
    }
    return getPublicRootInstance(fiberRoot);
}

legacyCreateRootFromDOMContainer

创建container为react挂载根节点

function legacyCreateRootFromDOMContainer(
    container: Container,
    forceHydrate: boolean,
): RootType 
{
    // 是否服务端渲染相关
    const shouldHydrate =
        forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    // First clear any existing content.
    if (!shouldHydrate) {
        let warned = false;
        let rootSibling;
        while ((rootSibling = container.lastChild)) {
            // ...判断节点属性
            // root移除lastChild
            container.removeChild(rootSibling);
        }
    }
    
    return createLegacyRoot(
        container,
        shouldHydrate ? {
            hydrate: true,
        } :
        undefined,
    );
}

createLegacyRoot

createLegacyRoot在react-dom\src\client\ReactDOMRoot.js

createLegacyRoot函数流程

  1. 返回ReactDOMBlockingRoot实例
  2. ReactDOMBlockingRoot实例_internalRoot等于new createRootImpl
// react-reconciler\src\ReactRootTags.js
// 标记是那个模式下,createLegacyRoot函数内使用
export const LegacyRoot = 0;
export const BlockingRoot = 1;
export const ConcurrentRoot = 2;

createRootImpl

都在react-reconciler目录下

createContainer主要步骤:

  1. createContainer -> 返回createFiberRoot实例
  2. createFiberRoot:
  • 调用FiberRootNode把div#root创建成fiber节点
  • createHostRootFiber创建空的fiber节点,把root.current执行空Fiber节点
  • initializeUpdateQueue创建更新的循环队列
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
{
  // Tag is either LegacyRoot or Concurrent Root
  // ...
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  // 在dom节点添加属性执行root的fiber节点
  markContainerAsRoot(root.current, container);
  const containerNodeType = container.nodeType;

  if (hydrate && tag !== LegacyRoot) {
    // 服务端相关
  } else if (
    containerNodeType !== DOCUMENT_FRAGMENT_NODE &&
    containerNodeType !== DOCUMENT_NODE
  ) {
    // 添加事件监听
    ensureListeningTo(container, 'onMouseEnter');
  }


  return root;
}

getPublicRootInstance

legacyRenderSubtreeIntoContainer最后执行返回的函数

  1. 获取当前 fiber 节点,即 rootFiber;
  2. rootFiber 还没有子节点,所以返回 null;
  3. 其他情况,返回 containerFiber.child.stateNode子节点的实例
export function getPublicRootInstance(
  container: OpaqueRoot,
): React$Component<anyany> | PublicInstance | null 
{
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  switch (containerFiber.child.tag) {
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      return containerFiber.child.stateNode;
  }
}
流程图
流程图

总结

  1. render只是入口函数,render会调用legacyRenderSubtreeIntoContainer进行挂载和更新
  2. 创建Fiber节点的函数是createRootImpl
  3. 执行updateContainer的时候是render和commit阶段入口函数
  4. 主要阶段:创建fiber -> 进行scheduled阶段 -> render阶段 -> commit阶段 -> 最后回到入口函数

参考资料

[1]

React技术揭秘: https://react.iamkasong.com/preparation/source.html#%E6%8B%89%E5%8F%96%E6%BA%90%E7%A0%81

[2]

React 源码讲解第 2 节-入口 API 之 ReactDOM.render: https://blog.csdn.net/weixin_44135121/article/details/108575542

早睡wqi

2021/09/12  阅读:26  主题:默认主题

作者介绍

早睡wqi