初步看了react-dom这个包的一些源码,发现其比react包要复杂得多,react包中基本不存在跨包调用的情况,他所做的也仅仅是定义了ReactElement对象,封装了对ReactElement的基本操作,而react-dom包存在复杂的函数调用。本文将对ReactDOM.render源码做一个初步解析。
文章中如有不当之处,欢迎交流指点。react版本16.8.2
。在源码添加的注释在github react-source-learn。
前言
使用react时常常写类似下面的代码:
import ReactDOM from 'react-dom';
ReactDOM.render(
Hello, world!
,
document.getElementById('root')
);
代码1
这里导入的ReactDOM就是packages/react-dom/client/ReactDOM.js中所导出的对象。从文档可见ReactDOM对象有如下几个方法:(ps:从源码看其实还有很多其他方法)
- render()
- hydrate()
- unmountComponentAtNode()
- findDOMNode()
- createPortal()
本文只介绍render()方法
代码分析
render方法定义如下:
render(
element: React$Element,
container: DOMContainer,
callback: ?Function,
) {
invariant(
// 1
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
warningWithoutStack(
!container._reactHasBeenPassedToCreateRootDEV,
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOM.%s(). This is not supported. ' +
'Did you mean to call root.render(element)?',
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
);
}
// 2
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
代码2
render方法接收两个必选参数可一个可选参数,结合代码1的调用可知,element是一个ReactElement对象, container是一个dom节点,callback在上面的代码1并没有指定,他是一个可选函数。
这个render方法做的事情比较简单,一是校验container参数,二是调用legacyRenderSubtreeIntoContainer方法并返回。
接下来是legacyRenderSubtreeIntoContainer
// 删除了第一次调ReactDOM.render不会走的分支
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component, // ReactDOM.render 是null
children: ReactNodeList, // 是一个ReactElement , ReactDOM.render是第一个参数
container: DOMContainer, // 是一个dom节点, ReactDOM.render是第二个参数
forceHydrate: boolean, // ReactDOM.render 是false
callback: ?Function, // ReactDOM.render 是 第三个参数
) {
if (__DEV__) {
topLevelUpdateWarnings(container);
}
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
// 根据type知道, Root type是个对象,包含
// render方法
// unmount方法
// legacy_renderSubtreeIntoContainer 方法
// createBatch 方法
// _internalRoot属性
let root: Root = (container._reactRootContainer: any);
if (!root) { // ReactDOM.render调用时走这里
// Initial mount
// 调用 legacyCreateRootFromDOMContainer 拿 Root
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate, // ReactDOM.render是false
);
// 在callback加参数
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 这个是packages/react-reconciler/ReactFiberScheduler.js中的方法
// TOLEARN: 这个里边应该是一些调度过程, 后续再看
unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else { // ReactDOM.render方法走这里
// 这里的root.render 返回的是一个叫Work的东西, TOLEARN,这个Work后面再做了解
root.render(children, callback);
}
});
}
// getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法
// 关于他是返回的一个什么东西, 后面再看, 总之他是我ReactDOM.render方法回调函数的一个参数,
// 也是返回值
return getPublicRootInstance(root._internalRoot);
}
legacyRenderSubtreeIntoContainer在第一次render时做了如下事情:
- 调用legacyCreateRootFromDOMContainer拿到一个ReactRoot的实例
- 在callback中注入一个参数instance
- 调用unbatchedUpdates开始一轮调度过程,这个是猜的
- 返回instance
关于instance的获取是调用的getPublicRootInstance,getPublicRootInstance是/packages/react-reconciler/ReactFiberReconciler.js中的方法,后面再研究
关于unbatchedUpdates, 这个东西是这个是packages/react-reconciler/ReactFiberScheduler.js中的方法,简单看了看还没太明白
接下来看一下ReactRoot实例的获取
// 删除了__DEV__分支的代码
// 若需要清理container的子节点,清理, 然后new ReactRoot并返回
function legacyCreateRootFromDOMContainer(
container: DOMContainer, // dom节点
forceHydrate: boolean, // false render
): Root {
// 是否不需要清理container的子元素, 第一次render是false, 即需要清理
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 第一次调用时为false
// First clear any existing content.
if (!shouldHydrate) { // 第一次render走这里
let warned = false;
let rootSibling;
// 这里将container的子元素都清理掉了
while ((rootSibling = container.lastChild)) {
container.removeChild(rootSibling);
}
}
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
这个方法比较简单,在第一次调用ReactDOM.render时,shouldHydrate会是false,所以会走到if (!shouldHydrate) 分支里,将container节点的所有子节点都清理掉,最后是new 了一个ReactRoot作为返回值,关于ReactRoot等内容将放到后面的文章分析。
小结
以上是ReactDOM.render的函数调用示意图。
- 首先在render方法中校验container参数是否合法,然后调用legacyRenderSubtreeIntoContainer
- 在legacyRenderSubtreeIntoContainer中, 调用legacyCreateRootFromDOMContainer拿到了ReactRoot的一个实例,调用getPublicRootInstance拿到了instance,用于注入到callback,和作为返回值,调用unbatchedUpdates开始调度过程
- 在legacyCreateRootFromDOMContainer中,首先清理了container中的所有子节点,然后new了一个ReactRoot并返回
TODO
- unbatchedUpdates是如何调度的
- ReactRoot是一个什么样的类
- dom的操作是在哪里