1、React更新的方式有三種:
(1)ReactDOM.render() || hydrate(ReactDOMServer渲染)
(2)setState
(3)forceUpdatejavascript
接下來,咱們就來看下ReactDOM.render()
源碼php
2、ReactDOM.render(element, container[, callback])html
做用:
在提供的container
裏渲染一個React
元素,並返回對該組件的引用前端
常見的用法是這個:java
ReactDOM.render(<App />, document.getElementById('root'));
複製代碼
官網網址:
zh-hans.reactjs.org/docs/react-…node
源碼:react
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()
git
(2)hydrate()
和render()
的惟一區別是傳入legacyRenderSubtreeIntoContainer()
的第四個參數不同:hydrate()
爲true
,表示在服務端儘量複用節點,提升性能;render()
爲false
,表示在瀏覽器端不會去複用節點,而是所有替換掉。github
3、legacyRenderSubtreeIntoContainer()web
做用:
初始化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
,稍後講解
4、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
5、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
6、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
7、ReactDOM.render()流程圖
總結:
ReactDOM.render() 的更新步驟
(1)建立 ReactRoot,ReactRoot 是建立整個React應用的根對象
(2)建立 FiberRoot 和 RootFiber
(3)建立更新 (建立更新後,就會進入調度階段,調度階段由調度器進行管理)
GitHub:
github.com/AttackXiaoJ…
(完)