Skip to content

Latest commit

 

History

History
50 lines (35 loc) · 3.36 KB

hook.md

File metadata and controls

50 lines (35 loc) · 3.36 KB

源码解析二十三 hook的结构与本质

React在 16.8 中更新了Hook,支持在 FunctionComponent中加入状态,以及一种全新的复用逻辑的方式。

无论使用哪种方式创建组件,在react中都会转化为fiber节点,每个fiber都有其stateupdateQueue,用来管理自身状态以及更新队列。在之前,FunctionComponent又被称为无状态组件,顾名思义,它的stateupdateQueue都为null,而hook的实现,就与这两个属性有关

以下是整个结构图

hook的结构

为了让function能让class那样setState()操控自身的状态,必然需要stateupdateQueue这两个属性,这个可以充分利用fiber自身的memoizedStateupdateQueue来实现。但是,这会带来一个问题,statefiber高度耦合,相互关联,如果要复用关于某个状态的逻辑,会把相关的fiber牵扯进来,这必然导致冗余。类似的实现如hocrender props,都会创建一个无用的fiber。复用逻辑的成本很大

所以,在hook的设计中,每个hook代表一个独立的状态单元,拥有其stateupdateQueue,可以随意塞入到任何的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只有一个memoizedStateupdateQueue,所以拿到state是非常容易的。而现在,由于hook本身就是一个独立的状态单元,一次FunctionComponent的执行可能伴随多个hook,也就会有N套stateupdateQueue,此时,如何确保每次更新时状态的精准性?确保每次更新时获取到的state都是正确的

为此,react采用了一种全新的思路,将hook设计成链表型的队列,并维护一个全局的currentHook指针

  • FunctionComponentmount阶段,调用到hook时生成相应的Hook单元,并将其插入到Hook队列末尾,调用结束后,将整个队列塞到fibermemoizedState

  • update时,会重新执行function,此时我们直接从memoizedState取出之前的队列,在调用到hook时,将currentHook指向当前队列头,对其上的状态进行对比以及处理更新,执行完毕后,再将currentHook指向队列中下一个hook。只要保证无论mount还是update时,它们的执行顺序是相同的,那么每次获取到的state就是精准的,所以这也hook能否正确使用最重要的一环,为此,react官方明确指出了hook的规则