react hook 原理
讲 hooks 之前提出一个问题:为什么 hooks 不能写在条件语句之中?
我们在初始化 hooks 的时候,fiber
的结构是长什么样的呢?
1function App() {
2 const [num, updateNum] = useState(0);
3 const [name, setName] = useState('alvin');
4 return null
5}
6// fiber 结构:
7{
8 // memoizedState:hooks 链表结构
9 memoizedState: {
10 queue: { pending: null },
11 memoizedState: 1,
12 next: { queue: { pending: null}, memoizedState: 'alvin', next: null }
13 },
14 stateNode: [Function: App]
15 // 其他属性...
16}
当我们执行 updateNum
怎么去更新我们的应用呢?
如上,创建一个 hooks
链表结构,存储在 fiber
的 memoizedState
属性上,next 指针指向下一个 hooks
创建更新对象
1const update = { action, next: null }
对于 App
来说,点击 p
标签产生的 update
的 action
为 num => num + 1
。
如果我们改写下 App
的 onClick
:
1// 之前
2return <p onClick={() => updateNum((num) => num + 1)}>{num}</p>;
3
4// 之后
5return (
6 <p
7 onClick={() => {
8 updateNum((num) => num + 1);
9 updateNum((num) => num + 1);
10 updateNum((num) => num + 1);
11 }}
12 >
13 {num}
14 </p>
15);
那么点击 p
标签会产生三个 update
。
合并更新
这些 update
是如何组合在一起呢?
答案是:他们会形成环状单向链表。
1function dispatchAction(queue, action) {
2 // 创建update
3 const update = { action, next: null }
4
5 // 环状单向链表操作
6 if (queue.pending === null) {
7 update.next = update
8 }
9 else {
10 update.next = queue.pending.next
11 queue.pending.next = update
12 }
13
14 queue.pending = update
15
16 // 模拟React开始调度更新
17 schedule()
18}
环状链表操作不太容易理解,这里我们详细讲解下。
当产生第一个update
(我们叫他u0
),此时queue.pending === null
。
update.next = update;
即u0.next = u0
,他会和自己首尾相连形成单向环状链表
。
然后queue.pending = update;
即queue.pending = u0
1queue.pending = u0 ---> u0
2 ^ |
3 | |
4 ---------
当产生第二个update
(我们叫他u1
),update.next = queue.pending.next;
,此时queue.pending.next === u0
,即u1.next = u0
。
queue.pending.next = update;
,即u0.next = u1
。
然后queue.pending = update;
即queue.pending = u1
1queue.pending = u1 ---> u0
2 ^ |
3 | |
4 ---------
你可以照着这个例子模拟插入多个update
的情况,会发现queue.pending
始终指向最后一个插入的update
。
这样做的好处是,当我们要遍历update
时,queue.pending.next
指向第一个插入的update
。
简单实现
详情略...
1let workInProgressHook
2let isMount = true
3
4// App组件对应的fiber对象
5const fiber = {
6 // 保存该FunctionComponent对应的Hooks链表
7 memoizedState: null,
8 // 指向App函数
9 stateNode: App,
10}
11
12function schedule() {
13 workInProgressHook = fiber.memoizedState
14 const app = fiber.stateNode()
15 isMount = false
16 return app
17}
18
19function dispatchAction(queue, action) {
20 // 创建update
21 const update = { action, next: null }
22
23 // 环状单向链表操作
24 if (queue.pending === null) {
25 update.next = update
26 }
27 else {
28 update.next = queue.pending.next
29 queue.pending.next = update
30 }
31
32 queue.pending = update
33
34 // 模拟React开始调度更新
35 schedule()
36}
37
38function useState(initialState) {
39 let hook
40
41 if (isMount) {
42 hook = {
43 queue: { pending: null },
44 memoizedState: initialState,
45 next: null,
46 }
47
48 if (!fiber.memoizedState)
49 fiber.memoizedState = hook
50 else
51 workInProgressHook.next = hook
52
53 workInProgressHook = hook
54 }
55 else {
56 hook = workInProgressHook
57 workInProgressHook = workInProgressHook.next
58 }
59
60 let baseState = hook.memoizedState
61 if (hook.queue.pending) {
62 let firstUpdate = hook.queue.pending.next
63
64 do {
65 const action = firstUpdate.action
66 baseState = action(baseState)
67 firstUpdate = firstUpdate.next
68 } while (firstUpdate !== hook.queue.pending)
69
70 hook.queue.pending = null
71 }
72 hook.memoizedState = baseState
73
74 return [baseState, dispatchAction.bind(null, hook.queue)]
75}
76
77function App() {
78 const [num, updateNum] = useState(0)
79
80 console.log(`${isMount ? 'mount' : 'update'} num: `, num)
81 return {
82 onClick() {
83 updateNum(num => num + 1)
84 },
85 }
86}
87
88const app = schedule()
89app.onClick()