tiny-react助你看懂React原始碼
tiny-react一個基於React17精簡而來的倉庫
tiny-react是一個為了簡化react原始碼學習的庫,和react17的區別就是少了很多功能,只實現了核心的邏輯,和preact這種react-like庫有著根本區別,preact更像是一個和react有著相同的介面但是實現細節卻不盡相同的react,而tiny-react是從react官方倉庫精簡而來,它更像官方react的閹割版,所以每一行程式碼,每一個函式都能在react最新的官方倉庫中找到出處,而且總共的程式碼只有6千多行,刨除掉ReactDOM只有4000多行,能讓React原始碼學習的難度大大降低
使用指南
在閱讀原始碼前,你需要對react的大體原理有一定的瞭解在這裡強烈推薦去通讀一下卡頌老師的React技術揭秘
對react的大體原理有一定了解後就可以開始看原始碼了,不過有些同學可能對一些原始碼中使用頻繁的的資料結構和演算法還不怎麼了解,這時候就可以看一下下面的看原始碼前需要了解的資料結構和演算法,如果你已經非常瞭解這些知識則可以跳過
react-dom這個模組,可以不用太關注,雖然他的程式碼有2000多行,但是大量的程式碼都是dom操作和事件委託相關函式,對學習React核心原理影響不大,不過其中 瀏覽器事件優先順序 相關的程式碼還是要注意一下
react-reconciler這個模組需要重點關注特別是其中的 ReactFiberWorkLoop 他是React原始碼的骨幹,把所有的模組連線到了一起
scheduler這個模組是程式碼最少,最簡單的模組了,而且基本沒有和其他模組耦合,可以直接單獨看他的原始碼
看原始碼前需要了解的資料結構和演算法
位運算
優先佇列
迴圈連結串列
dfs
Feature
時間切片
/**
* 開啟performance檢視Concurrent Mode下render階段的時間切片
* 點選一下按鈕然後會開始BitList的render階段,可以看到處於render階段(也就是點選按鈕後的前幾秒)時input
* 是可以輸入的,但是由於要渲染的東西太多,到commit階段時就會開始卡住,
* 所以此時會卡頓的瓶頸在瀏覽器渲染太耗時,而不是在react
*/
import
React
,
{
useState
}
from
‘。。/packages/react’
const
data
=
Array
。
from
({
length
:
50e4
},
(
_
,
i
)
=>
i
)
const
CHUNK_SIZE
=
1
e4
/
10
/**
* fiber是最小的工作粒度,如果要保證render過程中能保證瀏覽器能
* 處於互動的狀態就得保證一個fiber render的過程不會太耗
* 事件,所以可以根據機能設定合適的CHUNK_SIZE
* @param param0
* @returns
*/
const
Chunk
=
({
start
}
:
{
start
:
number
})
:
any
=>
{
const
end
=
Math
。
min
(
data
。
length
,
start
+
CHUNK_SIZE
)
const
children
=
Array
。
from
({
length
:
end
-
start
})
for
(
let
i
=
start
;
i
<
end
;
++
i
)
{
children
[
i
-
start
]
=
<
div
key
=
{
i
}>{
i
}
div
>
}
return
children
}
const
BigList
=
()
=>
{
const
children
=
[]
for
(
let
i
=
0
;
i
<
data
。
length
;
i
+=
CHUNK_SIZE
)
{
children
。
push
(<
Chunk
start
=
{
i
}
/>)
}
return
<
div
>{
children
}
div
>
}
export
const
TimeSlicingDemo
=
()
=>
{
const
[
isShowBigList
,
setIsShowBigList
]
=
useState
(
false
)
return
(
<
div
>
<
button
onClick
=
{()
=>
{
//由於點選事件內產生的更新會按Sync優先順序處理
//我們手動用setTimeout去掉點選事件的執行上下文
setTimeout
(()
=>
{
setIsShowBigList
(
!
isShowBigList
)
})
}}
>
toggle
BigList
button
>
<
br
/>
<
input
placeholder
=
“輸入點東西,看看互動有沒有被阻塞”
/>
<
br
/>
{
isShowBigList
?
<
BigList
/>
:
null
}
div
>
)
}
useState和useEffect
useLayoutEffect
優先順序排程
import
React
,
{
useState
,
useEffect
}
from
‘。。/packages/react’
export
const
PriorityScheduling
=
()
=>
{
const
[
count
,
updateCount
]
=
useState
(
0
)
const
onClick
=
()
=>
{
updateCount
((
count
)
=>
count
+
2
)
}
console
。
log
({
count
})
useEffect
(()
=>
{
//暫時不支援ref直接用選擇器獲取
const
button
=
document
。
getElementById
(
‘discretEventDispatcher’
)
!
setTimeout
(()
=>
updateCount
(
1
),
1000
)
setTimeout
(()
=>
{
button
。
click
()
//根據機能給第二個setTimeout一個合適的時間,或者適當的加長陣列的長度
//以保證點選事件觸發時,前一個低優先順序的更新的render階段還沒有完成
//才能看到插隊情況發生
},
1030
)
},
[])
return
(
<
div
>
<
button
id
=
“discretEventDispatcher”
onClick
=
{
onClick
}>
增加
2
button
>
<
div
>
{
Array
。
from
(
new
Array
(
10000
))。
map
((
v
,
index
)
=>
(
<
span
key
=
{
index
}>{
count
}
span
>
))}
div
>
div
>
)
}