1、初始化渲染

要将 React 元素渲染到页面中,分为两个阶段,render 阶段和 commit 阶段。

  • render 阶段负责创建 Fiber 数据结构并为 Fiber 节点打标记,标记当前 Fiber 节点要进行的 DOM 操作。
  • commit 阶段负责根据 Fiber 节点标记 ( effectTag ) 进行相应的 DOM 操作。

2、render阶段

2.1 reactDom.render()

文件位置:packages/react-dom/src/client/ReactDOMLegacy.js

  1. /**
  2. * 渲染入口
  3. * element 要进行渲染的 ReactElement, createElement 方法的返回值
  4. * container 渲染容器 <div id="root"></div>
  5. * callback 渲染完成后执行的回调函数
  6. */
  7. export function render(
  8. element: React$Element<any>,
  9. container: Container,
  10. callback: ?Function,
  11. ) {
  12. // 检测 container 是否是符合要求的渲染容器
  13. // 即检测 container 是否是真实的DOM对象
  14. // 如果不符合要求就报错
  15. invariant(
  16. isValidContainer(container),
  17. 'Target container is not a DOM element.',
  18. );
  19. return legacyRenderSubtreeIntoContainer(
  20. // 父组件 初始渲染没有父组件 传递 null 占位
  21. null,
  22. element,
  23. container,
  24. // 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染
  25. false,
  26. callback,
  27. );
  28. }

2.2 isValidContainer(不太重要)

文件位置:packages/react-dom/src/client/ReactDOMRoot.js

  1. /**
  2. * 判断 node 是否是符合要求的 DOM 节点
  3. * 1. node 可以是元素节点
  4. * 2. node 可以是 document 节点
  5. * 3. node 可以是 文档碎片节点
  6. * 4. node 可以是注释节点但注释内容必须是 react-mount-point-unstable
  7. * react 内部会找到注释节点的父级 通过调用父级元素的 insertBefore 方法, 将 element 插入到注释节点的前面
  8. */
  9. export function isValidContainer(node: mixed): boolean {
  10. return !!(
  11. node &&
  12. (node.nodeType === ELEMENT_NODE ||
  13. node.nodeType === DOCUMENT_NODE ||
  14. node.nodeType === DOCUMENT_FRAGMENT_NODE ||
  15. (node.nodeType === COMMENT_NODE &&
  16. (node: any).nodeValue === ' react-mount-point-unstable '))
  17. );
  18. }

2.3 初始化FiberRoot

下面的一大串代码也就是干了这几件事,主要是查找到createFiberRoot方法;

注意:标记绿色的方法可以不用看,主要是一步步的调用;

  • 创建 FiberRoot 和 rootFiber
  • 为 fiberRoot 添加 current 属性 值为 rootFiber
  • 为 rootFiber 添加 stateNode 属性 值为 fiberRoot

image.png

2.3.1 legacyRenderSubtreeIntoContainer(本段代码比较重要)

文件位置: packages/react-dom/src/client/ReactDOMLegacy.js

  1. /**
  2. * 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
  3. * parentComponent: 父组件, 初始渲染传入了 null
  4. * children: render 方法中的第一个参数, 要渲染的 ReactElement
  5. * container: 渲染容器
  6. * forceHydrate: true 为服务端渲染, false 为客户端渲染
  7. * callback: 组件渲染完成后需要执行的回调函数
  8. **/
  9. function legacyRenderSubtreeIntoContainer(
  10. parentComponent: ?React$Component<any, any>,
  11. children: ReactNodeList,
  12. container: Container,
  13. forceHydrate: boolean,
  14. callback: ?Function,
  15. ) {
  16. /**
  17. * 检测 container 是否已经是初始化过的渲染容器
  18. * react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性
  19. * react 会根据此属性进行不同的渲染方式
  20. * root 不存在 表示初始渲染
  21. * root 存在 表示更新
  22. */
  23. // 获取 container 容器对象下是否有 _reactRootContainer 属性
  24. let root: RootType = (container._reactRootContainer: any);
  25. // 即将存储根 Fiber 对象
  26. let fiberRoot;
  27. if (!root) {
  28. // 初始渲染
  29. // 初始化根 Fiber 数据结构
  30. // 为 container 容器添加 _reactRootContainer 属性
  31. // 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot
  32. // _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构
  33. // legacyCreateRootFromDOMContainer
  34. // createLegacyRoot
  35. // new ReactDOMBlockingRoot -> this._internalRoot
  36. // createRootImpl
  37. root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
  38. container,
  39. forceHydrate,
  40. );
  41. // 获取 Fiber Root 对象
  42. fiberRoot = root._internalRoot;
  43. /**
  44. * 改变 callback 函数中的 this 指向
  45. * 使其指向 render 方法第一个参数的真实 DOM 对象
  46. */
  47. // 如果 callback 参数是函数类型
  48. if (typeof callback === 'function') {
  49. // 使用 originalCallback 存储 callback 函数
  50. const originalCallback = callback;
  51. // 为 callback 参数重新赋值
  52. callback = function () {
  53. // 获取 render 方法第一个参数的真实 DOM 对象
  54. // 实际上就是 id="root" 的 div 的子元素
  55. // rootFiber.child.stateNode
  56. // rootFiber 就是 id="root" 的 div
  57. const instance = getPublicRootInstance(fiberRoot);
  58. // 调用 callback 函数并改变函数内部 this 指向
  59. originalCallback.call(instance);
  60. };
  61. }
  62. // 初始化渲染不执行批量更新
  63. // 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断
  64. // 所以不执行批量更新
  65. unbatchedUpdates(() => {
  66. updateContainer(children, fiberRoot, parentComponent, callback);
  67. });
  68. } else {
  69. // 非初始化渲染 即更新
  70. fiberRoot = root._internalRoot;
  71. if (typeof callback === 'function') {
  72. const originalCallback = callback;
  73. callback = function () {
  74. const instance = getPublicRootInstance(fiberRoot);
  75. originalCallback.call(instance);
  76. };
  77. }
  78. // Update
  79. updateContainer(children, fiberRoot, parentComponent, callback);
  80. }
  81. // 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值
  82. // 就是说渲染谁 返回谁的真实 DOM 对象
  83. return getPublicRootInstance(fiberRoot);
  84. }

2.3.2 legacyCreateRootFromDOMContainer

文件位置: packages/react-dom/src/client/ReactDOMLegacy.js

  1. /**
  2. * 判断是否为服务器端渲染 如果不是服务器端渲染
  3. * 清空 container 容器中的节点
  4. */
  5. function legacyCreateRootFromDOMContainer(
  6. container: Container,
  7. forceHydrate: boolean,
  8. ): RootType {
  9. // container => <div id="root"></div>
  10. // 检测是否为服务器端渲染
  11. const shouldHydrate =
  12. forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  13. // 如果不是服务器端渲染
  14. if (!shouldHydrate) {
  15. let rootSibling;
  16. // 开启循环 删除 container 容器中的节点
  17. while ((rootSibling = container.lastChild)) {
  18. // 删除 container 容器中的节点
  19. container.removeChild(rootSibling);
  20. /**
  21. * 为什么要清除 container 中的元素 ?
  22. * 为提供首屏加载的用户体验, 有时需要在 container 中放置一些占位图或者 loading 图
  23. * 就无可避免的要向 container 中加入 html 标记.
  24. * 在将 ReactElement 渲染到 container 之前, 必然要先清空 container
  25. * 因为占位图和 ReactElement 不能同时显示
  26. *
  27. * 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能
  28. * <div>
  29. * <p>placement<p>
  30. * <p>placement<p>
  31. * <p>placement<p>
  32. * </div>
  33. */
  34. }
  35. }
  36. return createLegacyRoot(
  37. container,
  38. shouldHydrate
  39. ? {
  40. hydrate: true,
  41. }
  42. : undefined,
  43. );
  44. }

2.3.3 createLegacyRoot

文件位置: packages/react-dom/src/client/ReactDOMRoot.js

  1. /**
  2. * 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot
  3. */
  4. export function createLegacyRoot(
  5. container: Container,
  6. options?: RootOptions,
  7. ): RootType {
  8. // container => <div id="root"></div>
  9. // LegacyRoot 常量, 值为 0,
  10. // 通过 render 方法创建的 container 就是 LegacyRoot
  11. return new ReactDOMBlockingRoot(container, LegacyRoot, options);
  12. }

2.3.4 ReactDOMBlockingRoot

文件位置: packages/react-dom/src/client/ReactDOMRoot.js

  1. /**
  2. * 类, 通过它可以创建 LegacyRoot 的 Fiber 数据结构
  3. */
  4. function ReactDOMBlockingRoot(
  5. container: Container,
  6. tag: RootTag,
  7. options: void | RootOptions,
  8. ) {
  9. // tag => 0 => legacyRoot
  10. // container => <div id="root"></div>
  11. // container._reactRootContainer = {_internalRoot: {}}
  12. this._internalRoot = createRootImpl(container, tag, options);
  13. }

2.3.5 createRootImpl

文件位置: packages/react-dom/src/client/ReactDOMRoot.js

  1. function createRootImpl(
  2. container: Container,
  3. tag: RootTag,
  4. options: void | RootOptions,
  5. ) {
  6. // container => <div id="root"></div>
  7. // tag => 0
  8. // options => undefined
  9. const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  10. markContainerAsRoot(root.current, container);
  11. return root;
  12. }

2.3.6 createContainer

文件位置: packages/react-reconciler/src/ReactFiberReconciler.js

  1. // 创建 container
  2. export function createContainer(
  3. containerInfo: Container,
  4. tag: RootTag,
  5. hydrate: boolean,
  6. hydrationCallbacks: null | SuspenseHydrationCallbacks,
  7. ): OpaqueRoot {
  8. // containerInfo => <div id="root"></div>
  9. // tag: 0
  10. // hydrate: false
  11. // hydrationCallbacks: null
  12. // 忽略了和服务器端渲染相关的内容
  13. return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
  14. }

2.3.7 createFiberRoot(本段代码是重点)

文件位置: packages/react-reconciler/src/ReactFiberRoot.js

  1. // 创建根节点对应的 fiber 对象
  2. export function createFiberRoot(
  3. containerInfo: any,
  4. tag: RootTag,
  5. hydrate: boolean,
  6. hydrationCallbacks: null | SuspenseHydrationCallbacks,
  7. ): FiberRoot {
  8. // 创建 FiberRoot
  9. const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  10. // 创建根节点对应的 rootFiber
  11. const uninitializedFiber = createHostRootFiber(tag);
  12. // 为 fiberRoot 添加 current 属性 值为 rootFiber
  13. root.current = uninitializedFiber;
  14. // 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
  15. uninitializedFiber.stateNode = root;
  16. // 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
  17. // updateQueue 用于存放 Update 对象
  18. // Update 对象用于记录组件状态的改变
  19. initializeUpdateQueue(uninitializedFiber);
  20. // 返回 root
  21. return root;
  22. }

2.3.8 initializeUpdateQueue

文件位置: packages/react-reconciler/src/ReactFiberRoot.js

  1. export function initializeUpdateQueue<State>(fiber: Fiber): void {
  2. const queue: UpdateQueue<State> = {
  3. baseState: fiber.memoizedState,
  4. baseQueue: null,
  5. shared: {
  6. pending: null,
  7. },
  8. effects: null,
  9. };
  10. fiber.updateQueue = queue;
  11. }