安裝導入 npm npm i three 導入 並非所有功能都在three,還需從子目錄導入 // three模塊 import * as three from 'three' // 一些不在three模塊的功能,這裡是OrbitControls導入示例 import { OrbitControls ...
theme: qklhk-chocolate
highlight: a11y-dark
react17放棄了之前的expirationTime而啟用了lane模型,故而在原來16的基礎上又產生了更多的二進位運算,在接下來的一段時間我打算把這些二進位運算都整明白了、
關於react為什麼會啟用lane模型的官方解釋
js中的二進位位運算都是以32位補碼的形式計算的,更多解釋可以參考mdn
1.關於上下文的切換
在react的更新中,executionContext
按照字面意思即為執行上下文,executionContext的預設值是0NoContext
, 此後的executionContext的更新中,都是與其他不同的上下文以按位或的運算的方式進行更新的,react17里的不同上下文有如下8種:
var NoContext =
/* */
0;
var BatchedContext =
/* */
1;
var EventContext =
/* */
2;
var DiscreteEventContext =
/* */
4;
var LegacyUnbatchedContext =
/* */
8;
var RenderContext =
/* */
16;
var CommitContext =
/* */
32;
var RetryAfterError =
/* */
64;
分析位運算可能寫出他們的二進位形式更加清晰一點,由上到下依次為:
NoContext 0000000
BatchedContext 0000001
EventContext 0000010
DiscreteEventContext 0000100
LegacyUnbatchedContext 0001000
RenderContext 0010000
CommitContext 0100000
RetryAfterError 1000000
實際參與計算時應該是32位的但是這裡取7位是因為2^6是64多餘的0對於我的分析來說是沒有意義的。
react17一共在11個地方對context進行了|=
方式的更新,不同的更新方式對應了不同的context的變數,比方,batchedUpdates的更新為executionContext |= BatchedContext;
,unbatchedUpdates中的更新則相應的為executionContext |= LegacyUnbatchedContext
。
這些更新在其他地方大同小異,基本都是|=
方式進行更新, 但在unbatchedUpdates更新前,卻進行了一次這個操作:executionContext &= ~BatchedContext
,這裡我們先不管這段代碼具體是什麼作用,我們暫且討論一下這個二進位運算會產生何種效果。首先假設在某一狀態時 executionContext 與 BatchedContext 發生了一次運算:
executionContext | BatchedContext
0000000 | 0000001
executionContext = 0000001
那麼 executionContext &= ~BatchedContext
:
executionContext & ~BatchedContext
0000001 & 1111110
executionContext = 0000000
可以看到executionContext &= ~BatchedContext
的效果其實就是還原了上次executionContext |= BatchedContext;
前的executionContext。這個其實很好理解,要知道所有的context他在以上的7位二進位中只占了其中的一位,那麼無論之前的executionContext與其中那個context進行按位或|
運算,其結果就是只是讓executionContext的某個位為1,拿BatchedContext舉例他只是使得executionContext右邊的第一位為1,而在按位取反之後,除了自己所占的那個位為0其餘都成了1,再與executionContext進行按位與&
運算,則executionContext
中其他位為1的(也就是說executionContext可能與其他上下文也一起進行了|
)是不會變得,只有~BatchedContext
對應的那一位現在是0,因此不管executionContext中這個位置的數字是0還是1其結果最後都為0,也就是上邊說的executionContext還原到了與BatchedContext|=
前的狀態。這條準則換成其他任何一個context都是一樣的。
2.關於上下文的判斷
拿一個地方舉例:
if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
{
throw Error( "Should not already be working." );
}
}
這個if里的表達式為真,即 左邊的要不等於NoContext,左邊的不等於NoContext則說明executionContext里屬於RenderContext或CommitContext的那個位為1,而那個位為1就說明 executionContext肯定與其中之一發生了按位或運算,而發生按位或也就代表著某個地方的代碼執行過了。以上。
小結: 其實以上這種二進位的運算,應該屬於二進位掩碼的應用,二進位的運算除了數學上的特征比方左移右移相當於乘以2除以2,其他方面更覺得像一種圖形一樣的運算,因為 & | 這兩種運算是不會產生進位的,因此看源碼的二進位運算更多的應該從類似圖形變換的角度去理解每個二進位運算的含義,而不是數字之間的運算。
3.關於lane
關於lane有篇文章個人決得講的特別好,推薦一看
關於lane的基本解釋
react17中使用了31位二進位來表示lane的概念,其中31位中占一位的變數稱作lane,占據多位的稱為lanes,react17中全部lane如下(二進位形式):
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;
關於lane的基本使用
1. 創建fiber
每一個fiber創建的時候其lanes,childLanes欄位都被初始化為NoLanes
2. 創建update
react中無論是初始的渲染,還是setstate或者由hooks派發出來的更新操作,都會調用createupdate
方法創建一個update對象,不同之處是,對於更新時的update對象來說lane欄位是什麼,是由與之相關的fiber的mode欄位決定的:
...
var lane = requestUpdateLane(fiber);
var update = createUpdate(eventTime, lane);
...
function requestUpdateLane(fiber) {
...
var mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return SyncLane;
} else if ((mode & ConcurrentMode) === NoMode) {
return getCurrentPriorityLevel() === ImmediatePriority$1 ? SyncLane : SyncBatchedLane;
}
...
}
mode一般來說有如下幾種:
var NoMode = 0;
var StrictMode = 1;
var BlockingMode = 2;
var ConcurrentMode = 4;
var ProfileMode = 8;
var DebugTracingMode = 16;
HostRootFiber(整個react應用的初始fiber節點)
初始化的時候,目前來看其tag
是LegacyRoot
,在createHostRootFiber
方法中賦予其mode:
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BlockingMode | StrictMode;
} else if (tag === BlockingRoot) {
mode = BlockingMode | StrictMode;
} else {
mode = NoMode;
}
由其tag可以知道HostRootFiber的mode即為noMode,之後在beginwork開始創建各個子節點的fiber時,其fiber的mode直接繼承自父節點:
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
因此 對於大部分fiber來說,在一次更新中由其派發的update的lane
是SyncLane。
3.更新過程中對fiber上各個欄位的更新
每個更新時都會自scheduleUpdateOnFiber
始,而在scheduleUpdateOnFiber
中,會
- 更新fiber上的lanes欄位:
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
然後沿fiber樹向上遍歷,更新每個父節點fiber的childLanes欄位
while (parent !== null) {
...
parent.childLanes = mergeLanes(parent.childLanes, lane);
...
}
其中mergeLanes 就是將兩個變數進行按位或運算,產生新的lanes。 即由此可以看到,當前各種各樣的更新的lane最終都會在根節點的childLanes欄位上有體現。
- 更新root根節點的各個欄位
- pendingLanes:
root.pendingLanes |= updateLane;
- suspendedLanes,pingedLanes
var higherPriorityLanes = updateLane - 1; // Turns 0b1000 into 0b0111
root.suspendedLanes &= higherPriorityLanes;
root.pingedLanes &= higherPriorityLanes;
幾句解釋: higherPriorityLanes - 1 ,比方代碼中的註釋Turns 0b1000 into 0b0111
,他假如是一個lanes欄位,那麼他的值就是比當前updateLane的優先順序更高的各個lane按位或之後的結果,因為結合前邊各個lane的值可以看到,越靠近右邊的1的位置的優先順序越高, 至於suspendedLanes,pingedLanes的更新就是保留了比當前優先順序更高的lane。
- eventTimes
這是一個31長度的數組,每一位對應一個lane
var eventTimes = root.eventTimes;
var index = laneToIndex(updateLane); // 獲取當前lane在eventTimes的數組索引
// We can always overwrite an existing timestamp because we prefer the most
/ recent event, and we assume time is monotonically increasing.
eventTimes[index] = eventTime;//eventTime是創建當前update的時間
未完待續。。。
- 我正在參與掘金技術社區創作者簽約計劃招募活動,點擊鏈接報名投稿。