ReactDOM.render函数是整个 React 应用程序首次渲染的入口函数,它的参数是什么,返回值是什么,函数内部做了什么?
ReactDOM.render(<App />, document.getElementById("root"));
前序
首先看下首次渲染时候,函数调用栈。
render函数源码
源码地址
export function render(
element: React$Element<any>, // 要渲染的组件
container: Container, // 根容器
callback: ?Function // 回调函数
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback
);
}
legacyRenderSubtreeIntoContainer函数
源码地址
内部实现:
- 判断是否为第一次渲染,也就是首次挂载
- 首次挂载,首先会创建
FiberRoot
,然后判断是否有回调函数,有就执行,最后进入updateContainer
。 - 非首次,获取
FiberRoot
,然后判断是否有回调函数,有就执行,最后进入updateContainer
。 - 首次和非首次,最大的区别在于是否创建
FiberRoot
和走不走批量更新。 - 最后返回
当前应用程序根组件的实例(getPublicRootInstance)
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
// 初始化也就是react, 第一次渲染
if (!root) {
// Initial mount
/**
* 首次挂载时,会通过 legacyCreateRootFromDOMContainer 方法创建 container._reactRootContainer 对象并赋值给 root
*/
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 也就是ReactDOM.render时候, 产生了fiberRoot
fiberRoot = root._internalRoot;
/**
* 判断是否存在callback, 也就是ReactDom.render中的第三个参数
*/
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
// 通过实例去调用originalCallback方法
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);
}
return getPublicRootInstance(fiberRoot);
}
legacyCreateRootFromDOMContainer函数
legacyCreateRootFromDOMContainer ===> 。。。 ===> createFiberRoot。
前面调用了一大堆函数,实际上最终目的就是创建Fiber,根据前序的截图来看,最终会去调用createFiberRoot
。
- 首先创建一个
FiberRootNode
- 然后创建
RootFiber
- 接着将两者关联起来
- 接着初始化队列
updateQueue
- 最后返回
FiberRootNode
fiberRootNode.current = rootFiber;
rootFiber.stateNode = fiberRootNode;
源码地址
createFiberRoot源码
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建fiberRoot(fiberRootNode)
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
const uninitializedFiber = createHostRootFiber(tag);// 创建rootFiber(fiberNode)
// 把rootFiber挂载到fiberRoot的current属性上
root.current = uninitializedFiber;
// 把fiberRoot挂载到rootFiber的stateNode属性上
uninitializedFiber.stateNode = root;
// 初始化更新队列
initializeUpdateQueue(uninitializedFiber);
return root;
}
updateContainer函数
源码地址
这个函数主要是计算当前节点的lane优先级,创建update对象,加入更新队列,最后被调度。这三个知识点不属于本文所细讲,留个印象,后面再细讲。
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function
): Lane {
// 获取fiberRoort.current, 也就是rootFiber, Fiber树的头结点
const current = container.current;
/* 当前触发更新时候的时间戳 */
const eventTime = requestEventTime();
// console.log(eventTime, performance.now())
// 计算当前节点lane(优先级)
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
/**
* 根据lane(优先级)计算当前节点的update对象
*/
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = { element };
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// 将update对象入队
enqueueUpdate(current, update);
// 调度当前的Fiber节点(也就是rootFiber)
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
解答开头提到的三个问题
参数是什么
- 要渲染的子组件
- 根容器
- 要执行的回调函数
返回值是什么
当前应用程序根组件的实例
做了什么操作
创建FiberRoot,计算lane优先级,创建update对象,加入更新队列,最后被调度器调度更新。