一文讲明白useState极简版

开篇

本文要求读者最起码大致了解:

  • 基本fiber更新流程
  • 基本fiber树渲染

不需要了解的很深刻也不需要了解完所有的更新渲染情况,但基本的流程需要已经了解过。如果没学习过的同学推荐先去学习一下上面两点,上面两点是整个react的骨架,useState等hook只是基于整个骨架运行的。

然后需要说一下的本文的react版本是17,react18将并发以及可中断渲染正式投入使用,所以react18的useState比react17复杂一些。有些人可能会问不是react17就有可中断渲染和并发了么,事实上在react17中可中断渲染虽然实现, 但是并没有在稳定版暴露出 api。

综上本文基于react17来进行讲解。什么?为啥不讲18,因为怕大家听不懂所以从相对简单的17开始讲,绝对不是作者自己不懂哈。

从一个问题开始讲解useState

直接从头开始讲整个useState我认为稍微有点枯燥并且东西太多一时半会儿get不到最核心的点。本文以下面这个问题入手开始讲useState我认为让读者有一个核心主线会更好理解,下面给出问题:

有以下代码,问一般情况下haha是何时执行

javascript 复制代码
const [data, setData] = useState(1);
cosnt haha = (now) => {return now + 1}
<div onClick={() => {setData(haha)}}>

有一种小白回答是,执行到setData的时候就开始执行,好吧,面试就这么答,答完就是,面试官:你还有什么问题要问我么。(狗头保命)

好吧,我们来认真回答这个问题,在17中,setState其实调用的是dispatchActiondispatchAction的参数从前往后三个分别是,当前useState所在的fiberhook.queue(存储update对象的环形链表)以及我们传入的haha函数。

入参说完了,来看看这个函数做了什么,我就不大片大片的贴源码了,给出链接大伙自己去康康,dispatchAction,其实就三件事情:

  • 创建update对象并将我们传入的haha函数作为他的action属性
  • update对象添加进hook.queue的环形链表中,最后在hook.queue.pending
  • 运行scheduleUpdateOnFiber发起新一轮调度

回到我们一开始的问题haha在何时执行,现在我们看看当前haha的位置是不是在:hook.queue.pending.action上,可以看到dispatchAction是不会执行haha的。好吧我们继续往下看,既然知道了haha的存放位置是一个环形链表,那看hook.queue什么时候被拿出来读取执行了。

由于执行了scheduleUpdateOnFiber所以我们又会走一遍fiber树构造和fiber树渲染的过程。我们知道这些要更新的状态肯定是在fiber树构造过程中更新的,所以视野聚焦到fiber树构造beginwork中renderWithHooks里的Component再次执行useState(1)的时候。

这里的useState实际上最后会调用updateReducer,这个函数会执行最终的haha,下面给出核心代码,解释都会放到注释里面。

javascript 复制代码
//...省略
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}
reducer = basicStateReducer;
if (update.eagerReducer === reducer) {
    newState = ((update.eagerState: any): S);
} else {
    const action = update.action;
    // action实际上就是我们的haha()
    newState = reducer(newState, action);
}
//...省略
hook.memoizedState = newState;
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];

所以回到题目haha 是什么时候执行的,答:更新fiber树时执行updateReducer时执行的。

看到这里,就会有疑惑,那按照这个说法,我初次渲染的时候应该也执行了haha,但是为什么我的data在初次渲染结果是1呢?

两种useState

事实上,useState在初次渲染和二次更新的时候对应指向的函数是不同的,初次渲染时是mountState,二次更新时是updateState两者实现有所不同。updateState所做的事情就是我上文所说的那样,而mountState做的事情会有一些不同,先说一下react是怎么区分何时使用哪一种函数的,下面给出核心代码。为什么能靠currentmemoizedState来做区分不用多说,不知道的还是回到我开题说的再去看看整个渲染流程。

javascript 复制代码
ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
    ? HooksDispatcherOnMount
    : HooksDispatcherOnUpdate;

最后useState默认就会去ReactCurrentDispatcher.current上拿对应的函数。

好吧,现在再来说一下mountState做了什么,其实很简单mountState总的来说就是四件事:

  • 创建了一下hook对象
  • 初始化hook对象属性(queue、memoizedState等)
  • 设置basicStateReducer
  • 返回当前状态, dispatch函数

好吧到这里其实问题基本都解开了。

拓展

这里拓展一个问题,什么情况会使得update.eagerReducer === reducertrue,这是useState一个性能优化点,可以去研究研究哦。

相关推荐
console.log('npc')1 分钟前
React 19 + Vite 企业级前端项目:从零搭建到规范交付
前端·react.js·前端框架
程序员黑豆7 分钟前
Java字符串常量池完全指南:原理、intern()方法与性能优化最佳实践
java·前端·ai编程
光影少年7 分钟前
react离线缓存、图片缓存方案
开发语言·前端·javascript·react native·react.js·缓存·前端框架
Canace13 分钟前
笔记本都合上了,Claude 为什么还能在手机上执行电脑上装的技能?
前端·人工智能·ai编程
cyadyx1 小时前
Vite 比 Webpack 构建效率更高
前端·webpack·node.js·vite
mCell3 小时前
AI 时代 SVG 画图的潜力
前端·agent·svg
我命由我123458 小时前
CesiumJS 笔记 - 获取容器中心点、Cartesian3 clone 方法、修改 Cartesian3 对象的高度
前端·javascript·css·前端框架·html·html5·js
Hopebearer_9 小时前
页面突然只剩 DOM?一次静态资源版本错配排查
前端·部署
抱抱宝10 小时前
Agent-study项目教程(03):手写 Mini-ReAct Agent(不依赖框架)
javascript·人工智能·gpt·react.js·prompt·agent
东风破_10 小时前
ESLint 是什么?为什么你的项目需要它?
前端·后端·代码规范