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
/**
* 渲染入口
* element 要进行渲染的 ReactElement, createElement 方法的返回值
* container 渲染容器 <div id="root"></div>
* callback 渲染完成后执行的回调函数
*/
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
// 检测 container 是否是符合要求的渲染容器
// 即检测 container 是否是真实的DOM对象
// 如果不符合要求就报错
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
return legacyRenderSubtreeIntoContainer(
// 父组件 初始渲染没有父组件 传递 null 占位
null,
element,
container,
// 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染
false,
callback,
);
}
2.2 isValidContainer(不太重要)
文件位置:packages/react-dom/src/client/ReactDOMRoot.js
/**
* 判断 node 是否是符合要求的 DOM 节点
* 1. node 可以是元素节点
* 2. node 可以是 document 节点
* 3. node 可以是 文档碎片节点
* 4. node 可以是注释节点但注释内容必须是 react-mount-point-unstable
* react 内部会找到注释节点的父级 通过调用父级元素的 insertBefore 方法, 将 element 插入到注释节点的前面
*/
export function isValidContainer(node: mixed): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(node.nodeType === COMMENT_NODE &&
(node: any).nodeValue === ' react-mount-point-unstable '))
);
}
2.3 初始化FiberRoot
下面的一大串代码也就是干了这几件事,主要是查找到createFiberRoot方法;
注意:标记绿色的方法可以不用看,主要是一步步的调用;
- 创建 FiberRoot 和 rootFiber
- 为 fiberRoot 添加 current 属性 值为 rootFiber
- 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
2.3.1 legacyRenderSubtreeIntoContainer(本段代码比较重要)
文件位置: packages/react-dom/src/client/ReactDOMLegacy.js
/**
* 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
* parentComponent: 父组件, 初始渲染传入了 null
* children: render 方法中的第一个参数, 要渲染的 ReactElement
* container: 渲染容器
* forceHydrate: true 为服务端渲染, false 为客户端渲染
* callback: 组件渲染完成后需要执行的回调函数
**/
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
/**
* 检测 container 是否已经是初始化过的渲染容器
* react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性
* react 会根据此属性进行不同的渲染方式
* root 不存在 表示初始渲染
* root 存在 表示更新
*/
// 获取 container 容器对象下是否有 _reactRootContainer 属性
let root: RootType = (container._reactRootContainer: any);
// 即将存储根 Fiber 对象
let fiberRoot;
if (!root) {
// 初始渲染
// 初始化根 Fiber 数据结构
// 为 container 容器添加 _reactRootContainer 属性
// 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot
// _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构
// legacyCreateRootFromDOMContainer
// createLegacyRoot
// new ReactDOMBlockingRoot -> this._internalRoot
// createRootImpl
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 获取 Fiber Root 对象
fiberRoot = root._internalRoot;
/**
* 改变 callback 函数中的 this 指向
* 使其指向 render 方法第一个参数的真实 DOM 对象
*/
// 如果 callback 参数是函数类型
if (typeof callback === 'function') {
// 使用 originalCallback 存储 callback 函数
const originalCallback = callback;
// 为 callback 参数重新赋值
callback = function () {
// 获取 render 方法第一个参数的真实 DOM 对象
// 实际上就是 id="root" 的 div 的子元素
// rootFiber.child.stateNode
// rootFiber 就是 id="root" 的 div
const instance = getPublicRootInstance(fiberRoot);
// 调用 callback 函数并改变函数内部 this 指向
originalCallback.call(instance);
};
}
// 初始化渲染不执行批量更新
// 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断
// 所以不执行批量更新
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);
}
// 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值
// 就是说渲染谁 返回谁的真实 DOM 对象
return getPublicRootInstance(fiberRoot);
}
2.3.2 legacyCreateRootFromDOMContainer
文件位置: packages/react-dom/src/client/ReactDOMLegacy.js
/**
* 判断是否为服务器端渲染 如果不是服务器端渲染
* 清空 container 容器中的节点
*/
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
// container => <div id="root"></div>
// 检测是否为服务器端渲染
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// 如果不是服务器端渲染
if (!shouldHydrate) {
let rootSibling;
// 开启循环 删除 container 容器中的节点
while ((rootSibling = container.lastChild)) {
// 删除 container 容器中的节点
container.removeChild(rootSibling);
/**
* 为什么要清除 container 中的元素 ?
* 为提供首屏加载的用户体验, 有时需要在 container 中放置一些占位图或者 loading 图
* 就无可避免的要向 container 中加入 html 标记.
* 在将 ReactElement 渲染到 container 之前, 必然要先清空 container
* 因为占位图和 ReactElement 不能同时显示
*
* 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能
* <div>
* <p>placement<p>
* <p>placement<p>
* <p>placement<p>
* </div>
*/
}
}
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
2.3.3 createLegacyRoot
文件位置: packages/react-dom/src/client/ReactDOMRoot.js
/**
* 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot
*/
export function createLegacyRoot(
container: Container,
options?: RootOptions,
): RootType {
// container => <div id="root"></div>
// LegacyRoot 常量, 值为 0,
// 通过 render 方法创建的 container 就是 LegacyRoot
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
2.3.4 ReactDOMBlockingRoot
文件位置: packages/react-dom/src/client/ReactDOMRoot.js
/**
* 类, 通过它可以创建 LegacyRoot 的 Fiber 数据结构
*/
function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// tag => 0 => legacyRoot
// container => <div id="root"></div>
// container._reactRootContainer = {_internalRoot: {}}
this._internalRoot = createRootImpl(container, tag, options);
}
2.3.5 createRootImpl
文件位置: packages/react-dom/src/client/ReactDOMRoot.js
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// container => <div id="root"></div>
// tag => 0
// options => undefined
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
markContainerAsRoot(root.current, container);
return root;
}
2.3.6 createContainer
文件位置: packages/react-reconciler/src/ReactFiberReconciler.js
// 创建 container
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
// containerInfo => <div id="root"></div>
// tag: 0
// hydrate: false
// hydrationCallbacks: null
// 忽略了和服务器端渲染相关的内容
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
2.3.7 createFiberRoot(本段代码是重点)
文件位置: packages/react-reconciler/src/ReactFiberRoot.js
// 创建根节点对应的 fiber 对象
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建 FiberRoot
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建根节点对应的 rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 为 fiberRoot 添加 current 属性 值为 rootFiber
root.current = uninitializedFiber;
// 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
uninitializedFiber.stateNode = root;
// 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
// updateQueue 用于存放 Update 对象
// Update 对象用于记录组件状态的改变
initializeUpdateQueue(uninitializedFiber);
// 返回 root
return root;
}
2.3.8 initializeUpdateQueue
文件位置: packages/react-reconciler/src/ReactFiberRoot.js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
baseQueue: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}