React
在 16.8 中更新了Hook
,支持在 FunctionComponent
中加入状态,以及一种全新的复用逻辑的方式。
无论使用哪种方式创建组件,在react
中都会转化为fiber
节点,每个fiber
都有其state
和updateQueue
,用来管理自身状态以及更新队列。在之前,FunctionComponent
又被称为无状态组件,顾名思义,它的state
和updateQueue
都为null
,而hook
的实现,就与这两个属性有关
以下是整个结构图
为了让function
能让class
那样setState()
操控自身的状态,必然需要state
和updateQueue
这两个属性,这个可以充分利用fiber
自身的memoizedState
和updateQueue
来实现。但是,这会带来一个问题,state
和fiber
高度耦合,相互关联,如果要复用关于某个状态的逻辑,会把相关的fiber
牵扯进来,这必然导致冗余。类似的实现如hoc
和render props
,都会创建一个无用的fiber
。复用逻辑的成本很大
所以,在hook
的设计中,每个hook
代表一个独立的状态单元,拥有其state
和updateQueue
,可以随意塞入到任何的fiber
中,相互之间没有联系,复用时只需要引入相应的hook
就好
interface Update<S, A> {
expirationTime: ExpirationTime,
action: A,
eagerReducer: ((s: S, a: A) => S),
eagerState: S,
next: Update<S, A>,
}
interface UpdateQueue<S, A> {
last: Update<S, A>, // update的环形链表
dispatch: ((a: A) => any),
eagerReducer: ((s: S, a: A) => S), // 记录当前的reducer,在dispatch时用于提前计算state
eagerState: S, // 计算当前的state,在dispath中做为提前计算的基值
}
interface Hook {
memoizedState: any, // 当前的state
baseState: any, // 记录低优先级的第一个跳过的state
baseUpdate: Update<any, any>, // 记录低优先级的第一个跳过的update
queue: UpdateQueue<any, any>, // 更新队列
next: Hook,
}
由于在之前的模式中,每个fiber
只有一个memoizedState
和updateQueue
,所以拿到state
是非常容易的。而现在,由于hook
本身就是一个独立的状态单元,一次FunctionComponent
的执行可能伴随多个hook
,也就会有N套state
和updateQueue
,此时,如何确保每次更新时状态的精准性?确保每次更新时获取到的state
都是正确的
为此,react
采用了一种全新的思路,将hook
设计成链表型的队列,并维护一个全局的currentHook
指针
-
在
FunctionComponent
的mount
阶段,调用到hook
时生成相应的Hook
单元,并将其插入到Hook
队列末尾,调用结束后,将整个队列塞到fiber
的memoizedState
上 -
在
update
时,会重新执行function
,此时我们直接从memoizedState
取出之前的队列,在调用到hook
时,将currentHook
指向当前队列头,对其上的状态进行对比以及处理更新,执行完毕后,再将currentHook
指向队列中下一个hook
。只要保证无论mount
还是update
时,它们的执行顺序是相同的,那么每次获取到的state
就是精准的,所以这也hook
能否正确使用最重要的一环,为此,react
官方明确指出了hook
的规则