十二、「深入React源码」--- 手写实现Hook之useState
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
一、Hook
1. 优点
- 在组件之间复用状态逻辑很难,可能要用到
render props
和高阶组件,React需要为共享状态逻辑提供更好的原生途径,Hook使你在无需修改组件结构的情况下,复用状态逻辑 - 复杂组件变得难以理解,Hook将组件中相互关联的部分拆成更小的函数(比如设置订阅/请求数据等)
- 不存在class难以理解的this,因为没有组件实例,性能上也比class好很多
2. 注意
- 只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用。
- 只能在React的函数组件中调用Hook,不要在其他JavaScript函数中调用。
二、useState
1. 介绍
useState
就是一个 Hook- 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state
useState
会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的this.setState
,但是它不会把新的 state 和旧的 state 进行合并useState
唯一的参数就是初始 state- 返回一个 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赋值,赋值有两种情况:
- 赋初始值
- 赋后来修改的值
初始值就是参数
initialState
。后来修改的值,需要用到我们的全局变量。我们把每个hook都存放进hookStates
,通过hookIndex
索引值可以拿到这一次要修改的值。(每次赋值后,我们都要把索引值+1,以便下次顺利进入到下一个Hook)
此时,我们使用setState
接收到的新的状态值newState
,进行赋值来更新状态。状态变化后,执行scheduleUpdate
更新方法。
2. 实现scheduleUpdate
因为我们是通过索引值来记录每一次需要修改的hook,因此在页面重现渲染之前,我们需要首先把hookIndex
索引值重置为0。之后进行一整个完整的dom-diff,去引发组件的更新
四、总结
- 更新是如何发生:
调用useState
,内部通过setState
修改状态后,调用scheduleUpdate
方法,从根节点执行完整的dom-diff比较,进行组件的更新。
- 为什么不能再条件语句或循环中使用Hook
从实现来看,每次hook的执行,都是从索引为0即第一个hook开始执行。也是依靠索引记录当前操作的Hook,假如使用条件语句或者循环,那么hook执行的顺序可能与我们在数组中存放的顺序不一致,就会乱掉。因此不能在条件语句或循环中使用Hook。
源码是使用链表:hook1.next = hook2.next === hook2
一样的道理,必须保证每次渲染的hook数量一致,并且指针顺序不能混乱。
本文实现的方式与真正的源码有一定的出入,待学完fiber之后,将会一比一按照源码实现。
与源码不同之处:
1. `hookState`全局变量存放状态,源码中hooks的状态时存放在当前节点的fiber对象中
2. `hookState`用的数组,源码中用的是单向链表
转载自:https://juejin.cn/post/7037360221768908807