作为React开发者,关于你能答上如下两个问题么: 1. 对于如下函数组件: 调用window.updateNum(1)可以将视图中的0更新为1么? 2. 对于如下函数组件: 在1秒内快速点击p5次,视图上显示为几?关于 其实,这两个问题本质上是关于在问: 本文会结合源码,讲透如上两个问题。关于 这些,关于就是关于你需要了解的关于useState的一切。 hook如何保存数据 FunctionComponent的关于render本身只是函数调用。 那么在render内部调用的关于hook是如何获取到对应数据呢? 比如: 答案是: 每个组件有个对应的fiber节点(可以理解为虚拟DOM),用于保存组件相关信息。关于 每次FunctionComponent render时,关于全局变量currentlyRenderingFiber都会被赋值为该FunctionComponent对应的关于fiber节点。 所以,关于hook内部其实是关于从currentlyRenderingFiber中获取状态信息的。 多个hook如何获取数据 我们知道,关于一个FunctionComponent中可能存在多个hook,比如: 那么多个hook如何获取自己的数据呢? 答案是: currentlyRenderingFiber.memoizedState中保存一条hook对应数据的香港云服务器单向链表。 对于如上例子,可以理解为: 当FunctionComponent render时,每执行到一个hook,都会将指向currentlyRenderingFiber.memoizedState链表的指针向后移动一次,指向当前hook对应数据。 这也是为什么React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。 useState执行流程 我们知道,useState返回值数组第二个参数为改变state的方法。 在源码中,他被称为dispatchAction。 每当调用dispatchAction,都会创建一个代表一次更新的对象update: 对于如下例子 调用updateNum(num + 1),会创建: 如果是多次调用dispatchAction,例如: 那么,update会形成一条环状链表。 这条链表保存在哪里呢? 既然这条update链表是由某个useState的dispatchAction产生,那么这条链表显然属于该useState hook。 我们继续补充hook的源码库数据结构。 其中,queue中保存了本次更新update的链表。 在计算state时,会将queue的环状链表剪开挂载在baseQueue最后面,baseQueue基于baseState计算新的state。 在计算state完成后,新的state会成为memoizedState。 为什么更新不基于memoizedState而是baseState,是因为state的计算过程需要考虑优先级,可能有些update优先级不够被跳过。所以memoizedState并不一定和baseState相同。更详细的解释见React技术揭秘[1] 回到我们开篇第一个问题: 调用window.updateNum(1)可以将视图中的0更新为1么? 我们需要看看这里的updateNum方法的具体实现: 可见,updateNum方法即绑定了currentlyRenderingFiber与queue(即hook.queue)的dispatchAction。 上文已经介绍,调用dispatchAction的目的云服务器是生成update,并插入到hook.queue链表中。 既然queue作为预置参数已经绑定给dispatchAction,那么调用dispatchAction就步仅局限在FunctionComponent内部了。 update的action 第二个问题 在1秒内快速点击p5次,视图上显示为几? 我们知道,调用updateNum会产生update,其中传参会成为update.action。 在1秒内点击5次。在点击第五次时,第一次点击创建的update还没进入更新流程,所以hook.baseState还未改变。 那么这5次点击产生的update都是基于同一个baseState计算新的state,并且num变量也还未变化(即5次update.action(即num + 1)为同一个值)。 所以,最终渲染的结果为1。 useState与useReducer 那么,如何5次点击让视图从1逐步变为5呢? 由以上知识我们知道,需要改变baseState或者action。 其中baseState由React的更新流程决定,我们无法控制。 但是我们可以控制action。 action不仅可以传值,也可以传函数。 在基于baseState与update链表生成新state的过程中: 可见,当传值时,由于我们5次action为同一个值,所以最终计算的newState也为同一个值。 而传函数时,newState基于action函数计算5次,则最终得到累加的结果。 如果这个例子中,我们使用useReducer而不是useState,由于useReducer的action始终为函数,所以不会遇到我们例子中的问题。 事实上,useState本身就是预置了如下reducer的useReducer。 总结 通过本文,我们了解了useState的完整执行过程。