Hooks的产生
1.组件状态:函数式组件被称为无状态组件,在没有hooks之前无法直接更新和管理状态。
2.生命周期:函数式组件没有像类式组件的生命周期方法,使用来模拟某些生命周期行为。
3.性能:函数式组件不需要维护实例对象,生命周期且没有内部状态,比类式组件具有更好的性能,更容易进行优化。
Hooks挂载数据的数据结构
在React16之前,直接递归遍历 VDOM即使用树状结构。当VDOM过大,频繁调用DOM API会出现耗时问题,且递归不能打断,同时具有性能问题。
后来引入了 fiber 架构(可中断 优先级调度 渐进式更新),将VDOM树转成fiber链表,再渲染 fiber。
Hooks存储数据
function组件初始化
初始化没有current树,完成一次组件更新后,会把当前workInProgress树赋值给current树。
renderWithHooks(
null,
workInProgress,
Component, //函数组件本身
props,
context,
renderExpirationTime,
);
function组件更新
renderWithHooks(
//当前组件的Fiber节点。在函数组件或者自定义Hook组件的渲染过程中,current指向当前正在执行的Fiber节点。
current,
//当前组件的workInProgress Fiber节点。在渲染过程中复用current节点并在其上执行更新操作的Fiber节点,用于实现渐进式更新、中断和恢复工作等
workInProgress,
//当前被调用的渲染函数
render,
//当前组件即将接收到的新的props属性值
nextProps,
//当前组件的上下文对象
context,
//当前渲染任务的到期时间
renderExpirationTime,
);
hooks数据的存储
上述的workingInProgress节点的 memorizedState 就是保存 hooks 数据的地方,它是一个通过 next 串联的链表。
Hooks存取数据的地方就在这里,执行的时候各自在自己的memorizedState 上存取数据,完成各种逻辑,这就是 hooks 的原理。
memorizedState链表在何时创建
链表在第一次创建时会执行mountXxx,后面在调用只需要链表只需要updateXxx。所以链表只被创建一次,后续只有更新的操作。
比如第一次调用 useState 会执行 mountState,后面再调用 useState 会执行 updateState。
每个hooks api创建对应的 memorizedState 对象,然后用 next 串联起来
useRef的实现
我们从最简单的useRef入手,每一个hooks都有mountXxx 和 updateXxx 两个阶段,比如 ref 就是 mountRef 和 updateRef。
mountWorkInProgressHook 在上面提到过,用来创建并返回 memorizedState 链表,同理下面的updateWorkInProgressHook 用来更新。
将传进来的initialValue 放在了一个有 current 属性的对象然后放在 memorizedState 属性上。
后面update时,没有做任何处理,直接返回这个对象。
所以useRef 的功能就是:保存一个数据的引用,引用不可变。
useCallback的实现
useCallback 在 memorizedState 上放了一个数组,第一个元素是传入的回调函数,第二个是传入的 deps(同时对 deps 做了下 undefined 的处理)。
更新的时候把之前的memorizedState取出来,和新传入的deps 做对比,如果没变,就返回之前的回调函数,即prevState[0]。如果变了,就创建一个新的数组,第一个元素是传入的回调函数,第二个是传入的 deps。
所以,useCallback 的功能:实现函数的缓存,如果 deps 没变就不会创建新的函数,否则返回新传入的函数。
useMemo的实现
useMemo的实现与useCallback相似,只不过将useCallback中在 memorizedState存上放的数组的第一个元素的回调函数改为函数执行结果。
更新的时候也是取出之前的 memorizedState,和新传入的 deps 做对比,如果没变,就返回之前的值,即prevState[0]。如果变了,创建一个新的数组放在 memorizedState,第一个元素是新传入函数的执行结果,第二个元素是 deps。
所以,useMemo 的功能:实现函数执行结果的缓存,如果 deps 没变,就返回之前的结果,否则才会执行函数返回最新结果。