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的实例
作用:
// 位于 react-reconciler/src/ReactFiber.js
export 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;
}
复制代码
流程图
最后是画的大致流程图
评论