在React当中,可以导致更新的方法有三种,setState、forceUpdate和ReactDOM.render这三种。当我们初始化应用时,会在入口js文件中调用ReactDOM.render函数去挂载组件。下面让我们来看看这个方法到底做了什么事情。
ReactDOM的代码在: /packages/react-dom/src/client/ReactDOM.js
我们先来看下ReactDOM的定义:
const ReactDOM: Object = {
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
// 这里省略了一些代码
};
再来看下ReactDOM.render的使用
ReactDOM.render(
<App />,
document.getElementById('root'),
() => console.log('callback')
);
通过上述例子可以看出render中每个参数所对应的含义是什么。接着让我们看看内部调用的legacyRenderSubtreeIntoContainer方法。
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
// 是否是ssr服务端渲染
forceHydrate: boolean,
callback: ?Function,
) {
// TODO: Ensure all entry points contain this check
// 如果container非法,则抛出异常
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
if (!root) {
// 第一次调用ReactDOM.render函数,会进入这个if条件
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
// 这里获取到<App/>的实例作为回调函数的上下文
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 这里函数设计到任务调度更新,牵扯的知识较多,在后续章节会进行讲解
// 这里可以暂且认为unbatchedUpdates内部的函数会被立即执行
unbatchedUpdates(() => {
if (parentComponent != null) {
// 这个暂时不需要管它,因为我们调用render的时候,parentComponent为false
// 只要关注else里面的就可以了
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return getPublicRootInstance(root._internalRoot);
}
当我们在render函数内部调用legacyRenderSubtreeIntoContainer方法时,parentComponent和forceHydrate为null和false,所以这里只需要先关心这两个条件下的逻辑。(forceHydrate表示是否是服务端渲染,当我们调用ReactDOM.hydrate时,这个值为true)
首先我们看最开始的几行代码
let root: Root = (container._reactRootContainer: any);
if (!root) {
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
}
这里在判断是否是第一次调用render,如果root不存在则说明是第一次,这时我们会去创建root。