likes
comments
collection
share

十二、「深入React源码」--- 手写实现Hook之useState

作者站长头像
站长
· 阅读数 16

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

一、Hook

1. 优点

  1. 在组件之间复用状态逻辑很难,可能要用到render props和高阶组件,React需要为共享状态逻辑提供更好的原生途径,Hook使你在无需修改组件结构的情况下,复用状态逻辑
  2. 复杂组件变得难以理解,Hook将组件中相互关联的部分拆成更小的函数(比如设置订阅/请求数据等)
  3. 不存在class难以理解的this,因为没有组件实例,性能上也比class好很多

2. 注意

  1. 只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用。
  2. 只能在React的函数组件中调用Hook,不要在其他JavaScript函数中调用。

二、useState

1. 介绍

  1. useState就是一个 Hook
  2. 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state
  3. useState会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并
  4. useState唯一的参数就是初始 state
  5. 返回一个 state,以及更新 state 的函数
    • 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同
    • setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列
const [state, setState] = useState(initialState)

三、思路

组件内部可能存在多个useState,每次改变状态都要触发setState修改当前要改变的state的值即可。因此我们需要借助几个全局变量:hookStates用来记录hook的值、hookIndex用来记录当前hook的索引、scheduleUpdate函数组件的更新

1. 实现useState

参考使用来看,useState接收一个入参initialState作为状态的初始值。我们先把当前的hook赋值,赋值有两种情况:

  1. 赋初始值
  2. 赋后来修改的值 初始值就是参数initialState。后来修改的值,需要用到我们的全局变量。我们把每个hook都存放进hookStates,通过hookIndex索引值可以拿到这一次要修改的值。(每次赋值后,我们都要把索引值+1,以便下次顺利进入到下一个Hook)

此时,我们使用setState接收到的新的状态值newState,进行赋值来更新状态。状态变化后,执行scheduleUpdate更新方法。

2. 实现scheduleUpdate

因为我们是通过索引值来记录每一次需要修改的hook,因此在页面重现渲染之前,我们需要首先把hookIndex索引值重置为0。之后进行一整个完整的dom-diff,去引发组件的更新

四、总结

  1. 更新是如何发生:

调用useState,内部通过setState修改状态后,调用scheduleUpdate方法,从根节点执行完整的dom-diff比较,进行组件的更新。

  1. 为什么不能再条件语句或循环中使用Hook

从实现来看,每次hook的执行,都是从索引为0即第一个hook开始执行。也是依靠索引记录当前操作的Hook,假如使用条件语句或者循环,那么hook执行的顺序可能与我们在数组中存放的顺序不一致,就会乱掉。因此不能在条件语句或循环中使用Hook。

源码是使用链表:hook1.next = hook2.next === hook2 一样的道理,必须保证每次渲染的hook数量一致,并且指针顺序不能混乱。

本文实现的方式与真正的源码有一定的出入,待学完fiber之后,将会一比一按照源码实现。
与源码不同之处:

1. `hookState`全局变量存放状态,源码中hooks的状态时存放在当前节点的fiber对象中
2. `hookState`用的数组,源码中用的是单向链表