hook作为FunctionComponent最重要的机制, 当然要了解一下它是如何存储的.

更新


[2019-7-17]

  • Initial release

前言


在分析hooks的运行机制过程中, 我通过一个简单的案例——如何使用hooks? 来引申出在页面渲染阶段, hooks是如何执行的.

当然, hooks的执行分为两个阶段:

  • mount 首次渲染阶段
  • update 后续更新阶段

本篇笔记将记录有关:

  • hooks是如何存储的
  • useState-hooks的基本结构

附上一篇笔记的链接:

记录


注意: 以下记录的有关hooks的内容, 均在mount阶段执行

mountState

先来看一下React.useState的主要源码部分:

展开源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {

// 创建新的hook对象, 并追加至`workInProgressHook`单向循环链表尾部
const hook = mountWorkInProgressHook();

if (typeof initialState === 'function') {
initialState = initialState();
}

hook.memoizedState = hook.baseState = initialState;

// 创建当前hook对象的更新队列, 按次序保存当前hook上产生的所有dispatch
const queue = hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};

// dispatch对应上述案例中的setName、setAge
// 而对于当前hook来说, 此时的dispatch = setName
const dispatch = queue.dispatch = dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
);

return [hook.memoizedState, dispatch];
}

mountWorkInProgressHook

mountWorkInProgressHook函数内部, 定义了hooks在的存储方式. 之前有一个误区, 看了很多类似的对于React-Hooks的分析文章, 都说hooks是以数组的形式存储的, 并对此深信不疑. 但是直到今天看了源码, 才恍然大悟, 可能由于版本变迁, 至少在react@16.8版本, hooks是以单向循环链表的形式存储在fiber上. 话不多说, 看一下源码:

展开源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,

baseState: null,
queue: null,
baseUpdate: null,

next: null,
};

if (workInProgressHook === null) {
// 当前的hook链表为空
firstWorkInProgressHook = workInProgressHook = hook;
} else {
// 将新的hook对象追加至hook链表尾部
workInProgressHook = workInProgressHook.next = hook;
}

return workInProgressHook;
}

hook

在当前函数组件中, 每定义一个hooksAPI, 对应的, 会创建一个新的hook对象, 看一下hook的类型定义:

展开源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
export type Hook = {
// 每次dispatch之后, 计算出来的新的state, 也就是hooks API返回的state
memoizedState: any,

// 保存被中断update的上一个update计算出来的更新
baseState: any,
// 保存被中断(优先级不足)update的上一个update
baseUpdate: Update<any, any> | null,
// update更新队列, 单向循环链表结构
queue: UpdateQueue<any, any> | null,

// 下一个update更新
next: Hook | null,
};

hook结构的几个重要的属性, 在mount阶段, 我并没有看懂, 转而看了一遍update的流程, 也就是在React.useState中, 自行调用dispatch来更新state, 之后才初步理解:

  • memoizedState
  • baseState
  • baseUpdate
  • queue
  • next

hook.memoizedState

顾名思义, 在React.useState中, 保存计算后的新state, 也就是下述代码返回的newState

1
const [newState, dispatch] = React.useState(initialState);

hook.baseState

在遍历updateQueue的过程中, 如果遍历到的updateexpirationTime小于整体更新的expirationTime, 表明当前的hooks产生的update的优先级较小, 不会在此次更新流程中执行, 从而导致被中断. 此时hook.baseState则保存上一次的update计算出来的state.

hook.baseUpdate

baseState一样, baseUpdate保存上一次被中断的更新的上一个update, 等到下一次renderRoot时, 先从baseUpdate开始.

hook.queue

hook.queueclass组件的updateQueue相似, 我姑且把它当成updateQueue吧, 不同的是:

class组件中, updateQueue保存的是整个类产生的更新
而在function组件中, updateQueue保存的是单个hooks产生的更新, 此时可能有多个hooks

再来看一下queue的结构:

1
2
3
4
5
6
7
8
9
10
type UpdateQueue<S, A> = {
// 最后一个update
last: Update<S, A> | null,
// 返回给用户的dispatch
dispatch: (A => mixed) | null,
// 上一次使用的reducer
lastRenderedReducer: ((S, A) => S) | null,
// 上一次渲染之后的state
lastRenderedState: S | null,
};

每个hooks产生的更新:

1
2
3
4
5
6
7
type Update<S, A> = {
expirationTime: ExpirationTime,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
next: Update<S, A> | null,
};

hook.next

下一个hook节点