共计 8469 个字符,预计需要花费 22 分钟才能阅读完成。
一、React 更新的方式有三种:
(1)ReactDOM.render() || hydrate(ReactDOMServer 渲染)
(2)setState
(3)forceUpdate
接下来,我们就来看下 ReactDOM.render()
源码
二、ReactDOM.render(element, container[, callback])
作用:
在提供的 container
里渲染一个 React
元素,并返回对该组件的引用
常见的用法是这个:
ReactDOM.render(<App />, document.getElementById('root'));
官网网址:
https://zh-hans.reactjs.org/docs/react-dom.html#render
源码:
const ReactDOM: Object = {
// 服务端使用 hydrate 方法渲染节点
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
invariant(isValidContainer(container),
'Target container is not a DOM element.',
);
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
//true 是让服务端尽可能复用节点,提高性能
true,
callback,
);
},
render(
// 元素
element: React$Element<any>,
// 容器
container: DOMContainer,
// 应用渲染结束后,调用的函数
callback: ?Function,
) {
// 错误抓取
invariant(isValidContainer(container),
'Target container is not a DOM element.',
);
//render 方法本质是返回了函数 legacyRenderSubtreeIntoContainer
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
//render 不会复用节点,因为是前端渲染
false,
callback,
);
},
}
解析:
(1)render()
方法本质是返回了函数legacyRenderSubtreeIntoContainer()
(2)hydrate()
和 render()
的唯一区别是传入 legacyRenderSubtreeIntoContainer()
的第四个参数不一样:hydrate()
为 true
,表示在服务端尽可能复用节点,提高性能;render()
为false
,表示在浏览器端不会去复用节点,而是全部替换掉。
三、legacyRenderSubtreeIntoContainer()
作用:
初始化Container
源码:
// null, element, container, false, callback,
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
//render 中一般渲染的是 DOM 标签,所以不会有_reactRootContainer 存在,// 所以第一次渲染,root 是不存在的
let root: _ReactSyncRoot = (container._reactRootContainer: any);
let fiberRoot;
if (!root) {
// Initial mount
// 创建一个 ReactRooter
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
fiberRoot = root._internalRoot;
// 判断是否有 callback
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
// 根据 fiberRoot 获取公共 Root 实例
// 就是 fiberRoot.current.child.stateNode
const instance = getPublicRootInstance(fiberRoot);
// 通过该实例 instance 去调用 originalCallback 方法
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 初始化安装不应该批量更新
unbatchedUpdates(() => {
//element,fiberRoot,null,callback
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);
}
解析:
(1)由于是第一次渲染更新,所以 root
是null
,只需看 !root
的情况
(2)legacyCreateRootFromDOMContainer(container,false,)
的作用是创建ReactRooter
,稍后会讲解
(3)unbatchedUpdates(fn)的简化源码如下:
unbatchedUpdates(fn){return fn()
}
(4)updateContainer()
的作用是更新container
,稍后讲解
四、legacyCreateRootFromDOMContainer(container,forceHydrate,)
作用:
创建一个ReactRooter
源码:
// 创建 ReactRooter
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): _ReactSyncRoot {
// 是否是服务端渲染
const shouldHydrate =
//render 的 forceHydrate 是 false,所以会调用 shouldHydrateDueToLegacyHeuristic 方法来判断是否是服务端渲染
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
// 如果不是服务端渲染的话
if (!shouldHydrate) {
let warned = false;
let rootSibling;
// 循环删除 container 的子节点
// 为什么要删除?因为 React 认为这些节点是不需要复用的
while ((rootSibling = container.lastChild)) {container.removeChild(rootSibling);
}
}
// Legacy roots are not batched.
//container 是空的 container,0,false
//ReactRoot 是同步的
//sync 同步
//async 异步
return new ReactSyncRoot(container, LegacyRoot, shouldHydrate);
}
解析:
(1)render()
的 forceHydrate
是false
,所以看 shouldHydrateDueToLegacyHeuristic(container)
是否返回false
① shouldHydrateDueToLegacyHeuristic()
作用:
判断是否是服务端渲染
源码:
// 判断是否是服务端渲染
function shouldHydrateDueToLegacyHeuristic(container) {
// 获取 container 的第一个节点(根节点)// 也就是 id='root' 的节点
const rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
// 判断是否是服务端渲染
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}
② getReactRootElementInContainer()
作用:
获取 container
中的第一个节点(或文档节点)
源码:
// 获取 Container 里的 RectRoot 元素
// 返回 document 节点或第一个子节点
function getReactRootElementInContainer(container: any) {if (!container) {return null;}
//DOCUMENT_NODE 即 window.document
if (container.nodeType === DOCUMENT_NODE) {return container.documentElement;} else {return container.firstChild;}
}
也就是说,判断是否是服务端渲染的标志 是:
在获取 container
中的第一个节点(或文档节点)后,看该节点是否有属性ROOT_ATTRIBUTE_NAME
ROOT_ATTRIBUTE_NAME
是什么呢?
// 服务端渲染的话,会在 React App 的第一个元素上添加该属性
// 以标识是服务端渲染的
export const ROOT_ATTRIBUTE_NAME = 'data-reactroot';
即data-reactroot
(2)由(1)可知,render()
的 container
的首节点是没有 data-reactroot
属性的,所以会进行 while
循环,依次删除 container
的子节点,删除完毕后,new 一个 ReactSyncRoot()
的实例
(3)ReactSyncRoot()
作用:
创建 ReactRoot
实例
源码:
// container,0,false
function ReactSyncRoot(
container: DOMContainer,
tag: RootTag,
hydrate: boolean,
) {
// Tag is either LegacyRoot or Concurrent Root
const root = createContainer(container, tag, hydrate);
this._internalRoot = root;
}
把创建的 root
作为 legacyCreateRootFromDOMContainer()
的__internalRoot
属性
① createContainer
作用:
创建 React 容器
源码:
// 创建 React 容器
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
): OpaqueRoot {
// 创建 FiberRoot
return createFiberRoot(containerInfo, tag, hydrate);
}
也就是说 legacyCreateRootFromDOMContainer()
的本质是创建了FilberRoot
五、updateContainer()
作用:
创建更新container
源码:
// 更新 Container
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
//1073741823
const currentTime = requestCurrentTime();
const suspenseConfig = requestCurrentSuspenseConfig();
// 计算过期时间,这是 React 优先级更新非常重要的点
const expirationTime = computeExpirationForFiber(
currentTime,
current,
suspenseConfig,
);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
suspenseConfig,
callback,
);
}
解析:
(1)requestCurrentTime()
作用:
计算新开始的时间
源码不用看,只需要知道该时间,是以 V8
引擎上最大 31
位整数 1073741823
为根据的:
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
// 整型最大数值,是 V8 中针对 32 位系统所设置的最大值
export default 1073741823;
(2)requestCurrentSuspenseConfig()
和 computeExpirationForFiber()
以后会讲解
(3)updateContainerAtExpirationTime()
作用:
每到过期时间,就更新container
,过期时间单位为 10ms
源码:
// 在过期时间内,更新 container
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
// 由于 parentComponent 为 null, 所以返回空对象{}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {container.context = context;} else {container.pendingContext = context;}
// 计划更新 Root
return scheduleRootUpdate(
current,
element,
expirationTime,
suspenseConfig,
callback,
);
}
解析:
① scheduleRootUpdate()
作用:
计划更新 Root
源码:
// 计划更新 Root
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
callback: ?Function,
) {
// 创建更新的时间节点
const update = createUpdate(expirationTime, suspenseConfig);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
if (revertPassiveEffectsChange) {flushPassiveEffects();
}
// 一整个 React 应用中,会有多次更新,而这多次更新均在更新队列中
enqueueUpdate(current, update);
// 进行任务调度
// 当 React 进行 Update 后,就要进行调度
// 即 根据任务的优先级去调度任务
// 先执行优先级高的任务,scheduleWork(current, expirationTime);
return expirationTime;
}
解析:
任务调度是 React
中最重要、复杂的内容,之后会慢慢来解析。
这里可以看到,React
将初始化的 Update
放入了更新队列中,并进行任务调度,最终返回了一个expirationTime
也就是说,updateContainer()
本质是返回了expirationTime
六、getPublicRootInstance()
作用:
获取 root 实例
源码:
// 获取 root 实例
export function getPublicRootInstance(container: OpaqueRoot,): React$Component<any, any> | PublicInstance | null {
// 看到 container.current,我就想到了 ref(xxx.current)// 获取当前节点
const containerFiber = container.current;
if (!containerFiber.child) {return null;}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
}
解析:
由于是 React 初始化,所以 container.current
是没有子节点的,所以该方法返回 null
七、ReactDOM.render()流程图
总结:
ReactDOM.render() 的更新步骤
(1)创建 ReactRoot,ReactRoot 是创建整个 React 应用的根对象
(2)创建 FiberRoot 和 RootFiber
(3)创建更新(创建更新后,就会进入调度阶段,调度阶段由调度器进行管理)
GitHub:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-dom/src/client/ReactDOM.js
(完)