写点什么

ReactDOM.render 在 react 源码中执行之后发生了什么?

作者:flyzz177
  • 2023-01-06
    浙江
  • 本文字数:6214 字

    阅读完需:约 20 分钟

ReactDOM.render

通常是如下图使用,在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。本文主要是将 ReactDOM.render 的执行流程在后续文章中会对创建更新的细节进行分析,文中的源代码部分为了方便阅读将__DEV__部分的代码移除掉了。


ReactDOM.render(  <App />,  document.getElementById('root'));
复制代码

render

位于:react-dom/src/client/ReactDOMLegacy.js


export function render(  element: React$Element<any>,  container: Container,  callback: ?Function,) {  // 验证container是否为有效的DOM节点  invariant(    isValidContainer(container),    'Target container is not a DOM element.',  );  return legacyRenderSubtreeIntoContainer(    null,    element,    container,    false,    callback,  );}
复制代码


返回了一个 legacyRenderSubtreeIntoContainer 函数,这里注意有 5 个参数


parentComponent: 父组件因为是初次创建所以为 null。


children: 传入的 ReactElement


container: 渲染 React 的 DOM 容器


forceHydrate: 判断是否需要协调,在服务端渲染的情况下已渲染的 DOM 结构是类似的因此可以在对比后进行复用。在服务端渲染的情况下使用 ReactDOM.hydrate()与 render() 相同只是 forceHydrate 会标记为 true。


callback: 渲染完成后的回调函数

legacyRenderSubtreeIntoContainer

位于:react-dom/src/client/ReactDOMLegacy.js


作用:


  • 判断是否为初次渲染,如果是就创建 root 并将 root._internalRoot 赋值给 fiberRoot 同时封装 callback 回调,然后调用 unbatchedUpdates 立即更新子节点。

  • 如果不是第一次渲染则进入正常的 updateContainer 流程。

  • 最后 getPublicRootInstance(fiberRoot)返回公开的 Root 实例对象。


function legacyRenderSubtreeIntoContainer(  parentComponent: ?React$Component<any, any>,  children: ReactNodeList,  container: Container,  forceHydrate: boolean,  callback: ?Function,) {
// TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. let root: RootType = (container._reactRootContainer: any); let fiberRoot; if (!root) { // Initial mount 初次渲染创建FiberRoot 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. unbatchedUpdates(() => { updateContainer(children, fiberRoot, parentComponent, callback); }); } else { fiberRoot = root._internalRoot; if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // Update updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot);}
复制代码

legacyCreateRootFromDOMContainer

位于:react-dom/src/client/ReactDOMLegacy.js


初次渲染进入创建 root 的环节:root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate)


作用:主要是判断是否为服务端渲染,如果是的话就会复用存在的 dom 节点进行协调(reconciliation)提高性能,如果不是则会清空 container 中的子元素,最后传入 container 和 shouldHydrate 返回 createLegacyRoot 函数。


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)) {      container.removeChild(rootSibling);    }  }
return createLegacyRoot( container, shouldHydrate ? { hydrate: true, } : undefined, );}
复制代码

createLegacyRoot

位于:react-dom/src/client/ReactDOMRoot.js


作用:返回了一个 ReactDOMBlockingRoot 实例,这里传入了 LegacyRoot 是一个常量=0 代表着现在使用的同步渲染模式,是为了后续的 Concurrent 可中断渲染模式做准备。


export function createLegacyRoot(  container: Container,  options?: RootOptions, // hydrate): RootType {  return new ReactDOMBlockingRoot(container, LegacyRoot, options);}
复制代码

ReactDOMBlockingRoot

位于:react-dom/src/client/ReactDOMRoot.js


作用:将createRootImpl函数的返回(FiberRoot)挂载到实例的_internalRoot 上


function ReactDOMBlockingRoot(  container: Container,  tag: RootTag,  options: void | RootOptions,) {  this._internalRoot = createRootImpl(container, tag, options);}
复制代码

createRootImpl

位于:react-dom/src/client/ReactDOMRoot.js


作用:执行 createContainer 拿到 FiberRootNode 并赋值给 root,再通过 markContainerAsRoot 将 RootFiber 挂载到 container 上。


function createRootImpl(  container: Container,  tag: RootTag,  options: void | RootOptions,) {  // Tag is either LegacyRoot or Concurrent Root  const hydrate = options != null && options.hydrate === true;  const hydrationCallbacks =    (options != null && options.hydrationOptions) || null;    // 拿到FiberRootNode  const root = createContainer(container, tag, hydrate, hydrationCallbacks);  // 将FiberRootNode挂载到container  markContainerAsRoot(root.current, container);  if (hydrate && tag !== LegacyRoot) {    const doc =      container.nodeType === DOCUMENT_NODE        ? container        : container.ownerDocument;    eagerlyTrapReplayableEvents(container, doc);  }  return root;}
复制代码

createContainer

位于:react-reconciler/src/ReactFiberReconciler.old.js 作用:返回 createFiberRoot


export function createContainer(  containerInfo: Container,  tag: RootTag,  hydrate: boolean,  hydrationCallbacks: null | SuspenseHydrationCallbacks,): OpaqueRoot {  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);}
复制代码

createFiberRoot

位于:react-reconciler/src/react-reconciler/src/ReactFiberReconciler.old.js 作用: 新建 FiberRoot 对象并赋值给 root,初始化 Fiber(通常叫做 RootFiber)通过 root.current = uninitializedFiber 和 uninitializedFiber.stateNode = root 将两者联系起来。


执行initializeUpdateQueue(uninitializedFiber)创建一个更新队列,挂载 fiber.updateQueue 下面


最后将 root 返回


相关参考视频讲解:进入学习


export function createFiberRoot(  containerInfo: any,  tag: RootTag,  hydrate: boolean,  hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot {  // 新建fiberRoot对象  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);  if (enableSuspenseCallback) {    root.hydrationCallbacks = hydrationCallbacks;  }
// Cyclic construction. This cheats the type system right now because // stateNode is any. //初始化RootFiber const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; // RootFiber的stateNode指向FiberRoot uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;}
复制代码

FiberRoot RootFiber 和 updateQueue

ReactDOM.render 主要创建了三个对象 FiberRooat、RootFiber 和 Updatequeue 下面我们这对这三个对象进行分析

FiberRoot

FiberRoot 是 FiberRootNode(containerInfo, tag, hydrate)的实例


位于:react-reconciler/src/ReactFiberRoot/FiberRootNode


作用:


  • 整个应用的起点

  • 包含应用挂载的目标节点

  • 记录整个应用更新过程的各种信息


function FiberRootNode(containerInfo, tag, hydrate) {  // 标记不同的组件类型  this.tag = tag;  // 当前应用对应的Fiber对象,是Root Fiber  // current:Fiber对象 对应的是 root 节点,即整个应用根对象  this.current = null;  // root节点,render方法接收的第二个参数  this.containerInfo = containerInfo;   // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到  this.pendingChildren = null;  this.pingCache = null;
//任务有三种,优先级有高低: //(1)没有提交的任务 //(2)没有提交的被挂起的任务 //(3)没有提交的可能被挂起的任务
//当前更新对应的过期时间 this.finishedExpirationTime = NoWork; //已经完成任务的FiberRoot对象,如果你只有一个Root,那么该对象就是这个Root对应的Fiber或null //在commit(提交)阶段只会处理该值对应的任务 this.finishedWork = null; // 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout(例如suspense返回的promise) this.timeoutHandle = noTimeout; // 顶层context对象,只有主动调用renderSubTreeIntoContainer时才会被调用 this.context = null; this.pendingContext = null; // 第一次渲染是否需要调和 this.hydrate = hydrate; // Node returned by Scheduler.scheduleCallback this.callbackNode = null; this.callbackPriority = NoPriority; //存在root中,最旧的挂起时间 //不确定是否挂起的状态(所有任务一开始均是该状态) this.firstPendingTime = NoWork; this.firstSuspendedTime = NoWork; this.lastSuspendedTime = NoWork; this.nextKnownPendingLevel = NoWork; //存在root中,最新的挂起时间 //不确定是否挂起的状态(所有任务一开始均是该状态) this.lastPingedTime = NoWork; this.lastExpiredTime = NoWork; this.mutableSourcePendingUpdateTime = NoWork;
if (enableSchedulerTracing) { this.interactionThreadID = unstable_getThreadID(); this.memoizedInteractions = new Set(); this.pendingInteractionMap = new Map(); } if (enableSuspenseCallback) { this.hydrationCallbacks = null; }}
复制代码

RootFiber

RootFiber 初始化于const uninitializedFiber = createHostRootFiber(tag) 通过 createFiber 返回 FiberNode的实例 作用:


  • 每个 ReactElement 对应一个 Fiber 对象

  • 记录节点的各种状态(方便了 hooks,因为记录 state 和 props 都是在 Fiber 只是完成后再挂载到 this 的例如:pendingProps pendingState memoizedProps memoizedState)

  • 串联整个应用形成树结构


// 位于 react-reconciler/src/ReactFiber.jsexport function createHostRootFiber(tag: RootTag): Fiber {  let mode;  if (tag === ConcurrentRoot) {    mode = ConcurrentMode | BlockingMode | StrictMode;  } else if (tag === BlockingRoot) {    mode = BlockingMode | StrictMode;  } else {    mode = NoMode;  }
return createFiber(HostRoot, null, null, mode);}
const createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,): Fiber { return new FiberNode(tag, pendingProps, key, mode);};
// FiberNode结构function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) { // Instance // 标记不同的组件类型 this.tag = tag; // ReactElement里面的key this.key = key; // ReactElement.type,也就是我们调用`createElement`的第一个参数 this.elementType = null; // 异步组件lazy component resolved之后返回的内容,一般是`function`或者`class`组件 this.type = null; // 对应节点的实例,比如类组件就是class的实例,如果是dom组件就是dom实例,如果是function component就没有实例这里为空 this.stateNode = null;
// Fiber Fiber是个链表通过child和Sibling连接,遍历的时候先遍历child如果没有子元素了则访问return回到上级查询是否有sibling // 指向他在Fiber节点树中的‘parent’,用来在处理完这个节点之后向上返回 this.return = null; // 指向第一个子节点 this.child = null; // 指向自己的兄弟节点,兄弟节点的return指向同一个副节点 this.sibling = null; this.index = 0;
this.ref = null; // 新的变动带来的新的props this.pendingProps = pendingProps; // 上次渲染完成后的props this.memoizedProps = null; // 该Fiber对应的组件产生的update会存放在这个队列(比如setState和forceUpdate创建的更新) this.updateQueue = null; // 上一次的state this.memoizedState = null;
this.dependencies = null; // this.mode = mode;
// Effects // 用来记录副作用 this.effectTag = NoEffect; // 单链表用来快速查找下一个side effect this.nextEffect = null; // 子树中第一个side effect this.firstEffect = null; // 子树中最后一个side effect this.lastEffect = null;
// 代表任务在未来的哪个时候应该被完成 就是过期时间 // 不包括他的子树产生的任务 this.expirationTime = NoWork; // 快速确定子树中是否有不再等待的变化 this.childExpirationTime = NoWork;
// Fiber树更新过程中,每个FIber都会有一个跟其对应的Fiber // 我们称他为`current <==> workInProgress` // 渲染完成后他们会交换位置 this.alternate = null;
// 调试相关的去掉了
}
复制代码

updateQueue

initializeUpdateQueue(uninitializedFiber);


位于:react-reconciler/src/ReactUpdateQueue.js


作用:单向链表,用来存放 update,next 来串联 update


关于 Update 和 UpdateQueue 涉及到的东西比较多打算单独一章来讲解


export function initializeUpdateQueue<State>(fiber: Fiber): void {  const queue: UpdateQueue<State> = {    // 每次操作完更新阿之后的state    baseState: fiber.memoizedState,    // 队列中的第一个`Update`    firstBaseUpdate: null,    // 队列中的最后一个`Update`    lastBaseUpdate: null,    shared: {      pending: null,    },    effects: null,  };  fiber.updateQueue = queue;}
复制代码

流程图

最后是画的大致流程图



用户头像

flyzz177

关注

还未添加个人签名 2021-12-07 加入

还未添加个人简介

评论

发布
暂无评论
ReactDOM.render在react源码中执行之后发生了什么?_React_flyzz177_InfoQ写作社区