React16 源碼簡(jiǎn)介
React16 重寫了核心算法 reconciler。因?yàn)?JavaScript 引擎線程和 UI 渲染線程雖然不是一個(gè)線程世分,但二者是互斥的(因?yàn)镴S運(yùn)行結(jié)果會(huì)影響到UI線程的結(jié)果)孽江,當(dāng)瀏覽器在處理 JavaScript 時(shí)绘搞,頁(yè)面就會(huì)停止渲染喂击,一旦 JavaScript 執(zhí)行占用的時(shí)間過長(zhǎng)昼榛,留給 UI 渲染的時(shí)間就會(huì)縮短税迷,從而造成頁(yè)面每秒渲染的幀數(shù)過低永丝,導(dǎo)致頁(yè)面產(chǎn)生明顯的卡頓感。
React 源碼中 package 下的幾個(gè)關(guān)鍵模塊:
- events:事件系統(tǒng)箭养。
- react:定義節(jié)點(diǎn)及其表現(xiàn)行為的包慕嚷,代碼量很少,JSX 依賴該模塊。
- react-dom:與更新和渲染相關(guān)喝检,取決于平臺(tái)(react-dom 和 react-native 中可能不同)嗅辣,依賴 react-reconciler、schedule挠说。
ReactDom.render
前言
ReactDom.render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
)
ReactDom.render 的第一個(gè)參數(shù)為 ReactElement澡谭,那么 element、component 和 instance 的關(guān)系是什么呢损俭?
element:一個(gè)可以描述 DOM 節(jié)點(diǎn)或者 component 實(shí)例的對(duì)象蛙奖,可以通過 React.createElement() 或者 JSX 語(yǔ)法創(chuàng)建。
component:可以通過 class 或者 function 聲明杆兵。
instance:只有類組件才有實(shí)例雁仲,React 會(huì)自動(dòng)創(chuàng)建實(shí)例。
component ??
class App extends React.Component {
render() {
<div>hello!</div>
}
}
element & instance ??
<App />
dom element ??
<div>hello!</div>
源碼閱讀
1.入口
// packages/react-dom/src/client/ReactDOM.js
render(
element: React$Element<any>, // 要渲染的元素
container: DOMContainer, // 根節(jié)點(diǎn)
callback: ?Function, // 渲染完執(zhí)行的回調(diào)
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
2.創(chuàng)建 root拧咳,然后調(diào)用 root.render伯顶,最終返回 DOM 節(jié)點(diǎn)信息
// packages/react-dom/src/client/ReactDOM.js
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, // null
children: ReactNodeList, //element
container: DOMContainer, //container
forceHydrate: boolean, //false
callback: ?Function, //callback
) {
let root: Root = (container._reactRootContainer: any);
// 首次渲染,root 不存在
if (!root) {
// Initial mount
// 創(chuàng)建 root
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate, // 在 render 中是 false骆膝,hydrate 中為 true祭衩,決定是否復(fù)用 DOM 節(jié)點(diǎn)
);
if (typeof callback === 'function') {
// 執(zhí)行回調(diào)
}
// Initial mount should not be batched.
// 不使用 batchedUpdates,因?yàn)槭状武秩拘枰M快完成
unbatchedUpdates(() => {
if (parentComponent != null) {
// ...
} else {
// render 中 parentComponent = null
root.render(children, callback);
}
});
} else {
// root 存在的情況
}
return getPublicRootInstance(root._internalRoot);
}
2.1 創(chuàng)建 root
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
// ...
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean,
) {
const root = createContainer(container, isConcurrent, hydrate);
this._internalRoot = root;
}
function createContainer(
containerInfo: Container,
isConcurrent: boolean,
hydrate: boolean,
): OpaqueRoot {
// 最終返回 FiberRoot 對(duì)象
return createFiberRoot(containerInfo, isConcurrent, hydrate);
}
2.2 調(diào)用 root.render
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback);
}
updateContainer(children, root, null, work._onCommit);
return work;
};
function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
const current = container.current;
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current, element, expirationTime, callback);
}
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// 創(chuàng)建更新
const update = createUpdate(expirationTime);
// 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;
}
// 將更新放入隊(duì)列
enqueueUpdate(current, update);
// 開始調(diào)度阅签。React16+提出了任務(wù)優(yōu)先級(jí)的概念掐暮。
scheduleWork(current, expirationTime);
return expirationTime;
}
function createUpdate(expirationTime: ExpirationTime): Update<*> {
return {
expirationTime: expirationTime,
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null,
};
}
2.3 返回真實(shí) DOM
HostComponent:抽象節(jié)點(diǎn),是 ClassComponent 的組成部分政钟。具體的實(shí)現(xiàn)取決于 React 運(yùn)行的平臺(tái)路克。在瀏覽器環(huán)境下就代表 DOM 節(jié)點(diǎn)。
Fiber:Fiber 是一個(gè)對(duì)象养交,表征 reconciliation 階段所能拆分的最小工作單元精算,和 React Instance 一一對(duì)應(yīng)。并通過 stateNode 屬性管理 Instance 的 local state碎连。
// packages/react-reconciler/src/ReactFiberReconciler.js
function getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
// container.current 為 container 對(duì)應(yīng)的 Fiber
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
// 判斷 Fiber 子節(jié)點(diǎn)的類型
switch (containerFiber.child.tag) {
// 真實(shí) DOM 如 div灰羽, span 等
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
// stateNode 是跟當(dāng)前 Fiber 相關(guān)聯(lián)的本地狀態(tài)(比如瀏覽器環(huán)境就是真實(shí)的 DOM 節(jié)點(diǎn))
return containerFiber.child.stateNode;
}
}
function getPublicInstance(instance: Instance): * {
return instance;
}
參考文獻(xiàn)
https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html