小熊源碼網(wǎng)新網(wǎng)站百度seo如何做
- 任務(wù)分割
- 異步執(zhí)行
- 讓出執(zhí)法權(quán)
文章目錄
- 1.React的設(shè)計理念
- 1.1 Fiber
- 1.2 Scheduler
- 1.3 Lane
- 1.4 代數(shù)效應(yīng)
- 2.React的源碼架構(gòu)
- 2.1 大概圖示
- 2.2 jsx
- 2.3 Fiber雙緩存
- 2.4 scheduler
- 2.5 Lane模型
- 2.6 reconciler
- 2.7 renderer
- 2.8 concurrent
- 3.React源碼調(diào)試
1.React的設(shè)計理念
- Fiber: 即對應(yīng)真實(shí)dom, 又作為分隔單元。
- Scheduler: 用js實(shí)現(xiàn)一套時間片運(yùn)行的機(jī)制, 使得requestIdleCallback()的瀏覽器的兼容性和觸發(fā)不穩(wěn)定的問題解決。
- Lane: 異步調(diào)度有了, 需要細(xì)粒度的管理各個任務(wù)的優(yōu)先級, 讓高優(yōu)先級的先執(zhí)行, 各個Fiber工作單元還能比較優(yōu)先級, 優(yōu)先級相同的一起執(zhí)行。
上面的機(jī)制能實(shí)現(xiàn)batchedUpdates批量更新和Suspense
1.1 Fiber
Fiber: react15的更新是同步的,因?yàn)樗荒軐⑷蝿?wù)分割,所以需要一套數(shù)據(jù)結(jié)構(gòu)讓它既能對應(yīng)真實(shí)的dom
又能作為分隔的單元
,這就是Fiber。
- 對應(yīng)真實(shí)dom。
- 作為分割單元。
let firstFiber
let nextFiber = firstFiber
let shouldYield = false
//firstFiber->firstChild->sibling
function performUnitOfWork(nextFiber){//...return nextFiber.next
}function workLoop(deadline){while(nextFiber && !shouldYield){nextFiber = performUnitOfWork(nextFiber)shouldYield = deadline.timeReaming < 1}requestIdleCallback(workLoop)
}requestIdleCallback(workLoop)
1.2 Scheduler
Scheduler: 有了Fiber, 需要用瀏覽器的時間片異步執(zhí)行這些Fiber的工作單元, 有一個Api是requestIdleCallback(), 可以在瀏覽器空閑的時候執(zhí)行一些任務(wù), 用這個api執(zhí)行react的更新。requestIdleCallback存在著瀏覽器的兼容性和觸發(fā)不穩(wěn)定的問題, 需要用js實(shí)現(xiàn)一套時間片運(yùn)行的機(jī)制, react稱為Scheduler。
1.3 Lane
Lane: 異步調(diào)度有了, 需要細(xì)粒度的管理各個任務(wù)的優(yōu)先級, 讓高優(yōu)先級的先執(zhí)行, 各個Fiber工作單元還能比較優(yōu)先級, 優(yōu)先級相同的一起執(zhí)行。
1.4 代數(shù)效應(yīng)
除了cpu的瓶頸問題, 還存在一些副作用, 比如獲取數(shù)據(jù)、文件操作等。不同設(shè)備性能和網(wǎng)絡(luò)狀況都不一樣, react如何處理這些問題, 需要react可以有分離副作用的能力, 解耦, 這就是代數(shù)效應(yīng)。
function getPrice(id) {return fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
}async function getTotalPirce(id1, id2) {const p1 = await getPrice(id1);const p2 = await getPrice(id2);return p1 + p2;
}async function run(){await getTotalPrice('001', '002');
}
getPrice()是一個異步獲取數(shù)據(jù)的方法, 可以用async+await的方式獲取數(shù)據(jù), 但是會導(dǎo)致調(diào)用getTotalPrice的run方法也會變成異步函數(shù), 這就是async的傳染性(副作用)。
function usePrice(id) {useEffect((id)=>{fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})}, [])
}function TotalPirce({id1, id2}) {const p1 = usePrice(id1);const p2 = usePrice(id2);return <TotalPirce props={...}>
}
getPrice換成usePrice, getTotalPirce換成TotalPirce組件, 這是hook的分離副作用能力。
generator: 也是有一定的傳染性的, generator不能計算優(yōu)先級, 排序優(yōu)先級。
function getPrice(id) {return fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
}function* getTotalPirce(id1, id2) {const p1 = yield getPrice(id1);const p2 = yield getPrice(id2);return p1 + p2;
}function* run(){yield getTotalPrice('001', '002');
}
解耦副作用在函數(shù)式編程的實(shí)踐中非常常見, 如react-saga, 將副作用從saga中分離, 自己不處理副作用, 發(fā)請求處理。
function* fetchUser(action) {try {const user = yield call(Api.fetchUser, action.payload.userId);yield put({type: "USER_FETCH_SUCCEEDED", user: user});} catch (e) {yield put({type: "USER_FETCH_FAILED", message: e.message});}
}
2.React的源碼架構(gòu)
- Scheduler(調(diào)度器): 排序優(yōu)先級,讓優(yōu)先級高的任務(wù)先進(jìn)行reconcile
- Reconciler(協(xié)調(diào)器): 找出哪些節(jié)點(diǎn)發(fā)生了改變,并打上不同的Flags(舊版本react叫Tag)
- Renderer(渲染器): 將Reconciler中打好標(biāo)簽的節(jié)點(diǎn)渲染到視圖上
2.1 大概圖示
jsx(mount/update) -> scheduler(render) -> reconciler(render) -> rerender(commit)
2.2 jsx
jsx: React通過Babel解析, 將jsx轉(zhuǎn)換成React.createElement, React.createElement方法返回virtual-dom對象React.createElement方法返回virtual-dom對象, 所有jsx本質(zhì)上就是React.createElement的語法糖。
createElement -> ReactElement
export function createElement(type, config, children) {let propName;// Reserved names are extractedconst props = {};let key = null;let ref = null;let self = null;let source = null;if (config != null) {if (hasValidRef(config)) {ref = config.ref;if (__DEV__) {warnIfStringRefCannotBeAutoConverted(config);}}if (hasValidKey(config)) {key = '' + config.key;}self = config.__self === undefined ? null : config.__self;source = config.__source === undefined ? null : config.__source;// Remaining properties are added to a new props objectfor (propName in config) {if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] = config[propName];}}}// Children can be more than one argument, and those are transferred onto// the newly allocated props object.const childrenLength = arguments.length - 2;if (childrenLength === 1) {props.children = children;} else if (childrenLength > 1) {const childArray = Array(childrenLength);for (let i = 0; i < childrenLength; i++) {childArray[i] = arguments[i + 2];}if (__DEV__) {if (Object.freeze) {Object.freeze(childArray);}}props.children = childArray;}// Resolve default propsif (type && type.defaultProps) {const defaultProps = type.defaultProps;for (propName in defaultProps) {if (props[propName] === undefined) {props[propName] = defaultProps[propName];}}}if (__DEV__) {if (key || ref) {const displayName =typeof type === 'function'? type.displayName || type.name || 'Unknown': type;if (key) {defineKeyPropWarningGetter(props, displayName);}if (ref) {defineRefPropWarningGetter(props, displayName);}}}return ReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props,);
}const ReactElement = function(type, key, ref, self, source, owner, props) {const element = {// This tag allows us to uniquely identify this as a React Element$$typeof: REACT_ELEMENT_TYPE,// Built-in properties that belong on the elementtype: type,key: key,ref: ref,props: props,// Record the component responsible for creating this element._owner: owner,};if (__DEV__) {// The validation flag is currently mutative. We put it on// an external backing store so that we can freeze the whole object.// This can be replaced with a WeakMap once they are implemented in// commonly used development environments.element._store = {};// To make comparing ReactElements easier for testing purposes, we make// the validation flag non-enumerable (where possible, which should// include every environment we run tests in), so the test framework// ignores it.Object.defineProperty(element._store, 'validated', {configurable: false,enumerable: false,writable: true,value: false,});// self and source are DEV only properties.Object.defineProperty(element, '_self', {configurable: false,enumerable: false,writable: false,value: self,});// Two elements created in two different places should be considered// equal for testing purposes and therefore we hide it from enumeration.Object.defineProperty(element, '_source', {configurable: false,enumerable: false,writable: false,value: source,});if (Object.freeze) {Object.freeze(element.props);Object.freeze(element);}}return element;
};
2.3 Fiber雙緩存
Fiber對象上面保存了包括這個節(jié)點(diǎn)的屬性, dom, 類型, 通過child, sibling, reture 形成fiber樹, 保存了更新狀態(tài)時用于計算state的updateQueue, updateQueue為一個鏈表, 上面存在未計算的update, update也是一個數(shù)據(jù)結(jié)構(gòu), 上面存有更新的數(shù)據(jù)、優(yōu)先級等, 還有副作用的信息。
雙緩存是指存在兩顆Fiber樹, current Fiber樹描述了當(dāng)前呈現(xiàn)的dom樹, workInProgress Fiber是正在更新的Fiber樹, 這兩樹都是存在于內(nèi)存, 在workInProgress Fiber構(gòu)建完成之后會將它作為current Fiber應(yīng)用到dom上。
function App() {const [count, setCount] = useState(0);return (<><h1 onClick={() => {setCount(() => count + 1);}}><p title={count}>{count}</p> </h1></>)
}ReactDOM.render(<App />, document.getElementById("root"));
2.4 scheduler
Scheduler的作用是調(diào)度任務(wù)。
在Scheduler中的每個任務(wù)的優(yōu)先級使用過期時間表示的, 如果一個任務(wù)的過期時間離現(xiàn)在很近, 說明要過期了, 優(yōu)先級很高。
沒有過期的放在timerQueue中, 過期的放taskQueue, timerQueue和taskQueue都是小頂堆, 所以peek出的都是離現(xiàn)在時間最近也就是優(yōu)先級最高的那個任務(wù)。
2.5 Lane模型
優(yōu)先級表示方法Lane: Lane使用二進(jìn)制的方式表示優(yōu)先級, 1表示位置, 同一個二進(jìn)制數(shù)可以有多個相同優(yōu)先級的位, 這就可以表示‘批’的概念。低優(yōu)先級的任務(wù)如果被高優(yōu)先級的任務(wù)一直打斷, 等到達(dá)它的時候, 優(yōu)先級自動變?yōu)樽罡摺?/p>
bit越多, 優(yōu)先級越低。
//ReactFiberLane.js
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;const NonIdleLanes = /* */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
2.6 reconciler
Reconciler發(fā)生在render階段, render分為節(jié)點(diǎn)執(zhí)行beginWork和completeWork, 或是計算state, 對比節(jié)點(diǎn)的差異, 為節(jié)點(diǎn)賦值相應(yīng)的effectFlags。
Reconciler會創(chuàng)建或者更新Fiber節(jié)點(diǎn)。在mount的時候會根據(jù)jsx生成Fiber對象,在update的時候會根據(jù)最新的state形成的jsx對象和current Fiber樹對比構(gòu)建workInProgress Fiber樹, 對比就是diff算法。
diff算法發(fā)生在render階段的reconcileChildFibers函數(shù)中, diff算法分為單節(jié)點(diǎn)的diff和多節(jié)點(diǎn)的diff, 單節(jié)點(diǎn)會根據(jù)節(jié)點(diǎn)的key和type, props判斷節(jié)點(diǎn)是復(fù)用還是直接新創(chuàng)建節(jié)點(diǎn), 多節(jié)點(diǎn)diff會涉及節(jié)點(diǎn)的增刪和節(jié)點(diǎn)位置的變化。
reconcile時會在這些Fiber上打上Flags標(biāo)簽, 在commit階段把這些標(biāo)簽應(yīng)用到真實(shí)dom上, 這些標(biāo)簽代表了節(jié)點(diǎn)的增刪改。
//ReactFiberFlags.js
export const Placement = /* */ 0b0000000000010;
export const Update = /* */ 0b0000000000100;
export const PlacementAndUpdate = /* */ 0b0000000000110;
export const Deletion = /* */ 0b0000000001000;
render階段遍歷Fiber樹類似dfs的過程, ‘捕獲’階段發(fā)生在beginWork函數(shù)中, 該函數(shù)做的主要工作是創(chuàng)建Fiber節(jié)點(diǎn), 計算state和diff算法, ‘冒泡’階段發(fā)生在completeWork中, 該函數(shù)主要是做一些收尾工作, 例如處理節(jié)點(diǎn)的props、和形成一條effectList的鏈表, 該鏈表是被標(biāo)記了更新的節(jié)點(diǎn)形成的鏈表。
function App() {return (<><h1><p>count</p> xiaochen</h1></>)
}
function App() {const [count, setCount] = useState(0);return (<><h1onClick={() => {setCount(() => count + 1);}}><p title={count}>{count}</p> xiaochen</h1></>)
}
如果p和h1節(jié)點(diǎn)更新了則effectList如下, rootFiber->h1->p, fiberRoot是整個項目的根節(jié)點(diǎn), rootFiber為應(yīng)用的根節(jié)點(diǎn), 可以有多個。
2.7 renderer
Renderer發(fā)生在commit階段, commit階段遍歷effectList執(zhí)行對應(yīng)的dom操作或部分生命周期。并執(zhí)行真實(shí)dom節(jié)點(diǎn)的操作和一些生命周期, 不同的平臺對應(yīng)的Renderer不同, 瀏覽器對應(yīng)的是react-dom。
commit階段發(fā)生在commitRoot函數(shù)中, 遍歷effectList, 三個函數(shù)來處理effectList上的節(jié)點(diǎn), commitBeforeMutationEffects, commitMutationEffects, commitLayoutEffects。
2.8 concurrent
一類功能的合集(如fiber、schduler、lane、suspense), 目的是為了提高應(yīng)用的響應(yīng)速度, 使應(yīng)用cpu密集型的更新不在那么卡頓, 核心是實(shí)現(xiàn)了一套異步可中斷、帶優(yōu)先級的更新。
3.React源碼調(diào)試
- fixtures:為代碼貢獻(xiàn)者提供的測試React
- packages:主要部分,包含Scheduler,reconciler等
- scripts:react構(gòu)建相關(guān)
-
react:核心Api如:React.createElement、React.Component都在這
-
和平臺相關(guān)render相關(guān)的文件夾:
react-art:如canvas svg的渲染
react-dom:瀏覽器環(huán)境
react-native-renderer:原生相關(guān) react-noop-renderer:調(diào)試或者fiber用 -
試驗(yàn)性的包
react-server: ssr相關(guān)
react-fetch: 請求相關(guān)
react-interactions: 和事件如點(diǎn)擊事件相關(guān)
react-reconciler: 構(gòu)建節(jié)點(diǎn)
shared:包含公共方法和變量 -
輔助包:
react-is : 判斷類型
react-client: 流相關(guān)
react-fetch: 數(shù)據(jù)請求相關(guān) -
react-refresh: 熱加載相關(guān)
-
scheduler:調(diào)度器相關(guān)
-
React-reconciler:在render階段用它來構(gòu)建fiber節(jié)點(diǎn)