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 链表结构,存储在 fibermemoizedState 属性上,next 指针指向下一个 hooks

创建更新对象

1const update = { action, next: null }

对于 App 来说,点击 p 标签产生的 updateactionnum => num + 1

如果我们改写下 ApponClick

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()