React深入useState/setState

简介: 本文适合对React感兴趣的小伙伴,特别是想深入学习React的小伙伴

一、前言


本文基于开源项目:

https://github.com/facebook/react

 

useState()hook中用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。 


setState()class组件中,用于对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。    


下面且听广东靓仔徐徐道来~


二、setState 同步or异步?why?


   细心的小伙伴在看React官方文档的时候,应该有看到过以下的描述:

  • setState——数据驱动视图
  • setState——工作机制太复杂


广东靓仔先给出结论

   在React16.8之前,setState() 并不是单纯同步/异步的,它的表现会因调用场景不同而不同。在 React 钩子函数及合成事件中,它表现为异步;而在 setTimeout、setInterval 等函数中,包括在 DOM 原生事件中,它都表现为同步。有了hook后,也就是16.8版本后,fiber对state修改都是异步了。


接下来我们来仔细分析setState() 

调用之后,React内做了什么?我们先来看一张图,如下所示:

image.png


   从上面的图中我们可以看出,当我们调用setState() 之后,在生命周期中会进行 re-render,也就是我们常说的重渲染。而re-render会涉及对Dom的操作,这会有较大的开销。不难看出一个完整的更新流程里面涉及到很多操作。  

 

这里我们大胆猜测,如果每次使用setState() ,都要走一遍完整更新流程,无疑刷新几次就直接崩了。


下面我们通过两个简单的Demo来说明,这里广东靓仔采用了React15版本截取了部分代码:


testSetState = () => {
  console.log('前端早茶, age:', this.state.age)
  for(let i = 0; i < 18; i++) {
    this.setState({
      count: this.state.age + 1
    })
  }
  console.log('前端早茶,循环18次后age:', this.state.age)
}


对React很熟悉的小伙伴很容易就可以看出,输出如下:

前端早茶, age:0

前端早茶,循环18次后age:0


从答案我们可以得出,尽管是for循环setState() React内是会把每一个setState放到队列里边,等到合适的时机再把最新的State执行一遍更新流程。我们可以把这个称为“批量更新”,也就是会合并state任务。


再来一个简单的Demo:


reduce = () => {
  setTimeout(() => {
    console.log('前端早茶, 前age:', this.state.age)
    this.setState({
      age: this.state.age + 1
    });
    console.log('前端早茶,后的age', this.state.age)
  },0);
}

 

我们会发现age会++,很显然,是这个setTimeout 起到了某种作用。我们试着把setTimeout 去掉,又不会++了。这里广东靓仔跟小伙伴们讲,其实并不是setTimeout了 setState,只是 setTimeout 让 setState 脱离了React对setState的管控。因此只要是在 React 管控下的 setState,一定是异步的。

对这里的调用链感兴趣的小伙伴可以自行去阅读源码~


三、useState实现机制


   开始讲解机制之前,广东靓仔先说个题外话,除了我们在React官网看到的hook API,其他的都是属于自定义hook。


官网hook,如下图所示:

image.png

   广东靓仔使用React16.8版本进行讲解,这里稍微注意下,我们在使用hook api的时候,别在其前面加if 哦。如果你加了if又没有使用eslint,会报错哦。当然拉,如果你之前看了前端早茶的React17那篇文章,开始使用了React17,会直接给你提示 *** is not a function了~  

 

下面我们开始梳理useState() 来分析Hook的调用链路,广东靓仔通过process on画了下面这张图:

image.png


   从上面这个图中我们可以看出,useState首次渲染会触发一些操作,最后会执行mountState,下面我们看看这个核心函数。


function mountState(initialState) {
  // 将新的 hook 对象追加进链表尾部
  var hook = mountWorkInProgressHook();
  // initialState 可以是一个回调,若是回调,则取回调执行后的值
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  // 创建当前 hook 对象的更新队列,这一步主要是为了能够依序保留 dispatch
  const queue = hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  };
  // 将 initialState 作为一个“记忆值”存下来
  hook.memoizedState = hook.baseState = initialState;
  // dispatch 是由上下文中一个叫 dispatchAction 的方法创建的
  var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
  // 返回目标数组
  return [hook.memoizedState, dispatch];
}


   从上面代码可以看出mounState 的主要工作是初始化 Hooks,dispatch其实就是我们经常看到的setxxx函数。


新的 hook 对象追加进链表尾部,我们下面来看看核心函数mountWorkInProgressHook :


function mountWorkInProgressHook() {
  // 单个 hook 是以对象的形式存在的
  var hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  };
  if (workInProgressHook === null) {
    // 将 hook 作为链表的头节点处理
    firstWorkInProgressHook = workInProgressHook = hook;
  } else {
    // 若链表不为空,则将 hook 追加到链表尾部
    workInProgressHook = workInProgressHook.next = hook;
  }
  // 返回当前的 hook
  return workInProgressHook;
}


从上面代码中我们可以看到hook 对象之间是用单向链表的形式相互串联。


最后我们来看看更新过程,同样上图:

image.png


   从上图可以看出,我们更新的时候是调用updateState调用链路其实很好理解:它按顺序遍历之前构建好的链表,读取对应的数据信息进行渲染。    从上面的内容,我们很容易得出结论,Hooks 的本质其实是链表


四、总结


   平时我们在使用React开发项目的时候,有时会遇到一些莫名bug,然后通过搜索引擎寻找解决方式。当我们解决完bug后,广东靓仔建议大家做个小结,顺便举一反三,顺便把某原理梳理梳理。下次就能很好的避免这些问题。

相关文章
|
3天前
|
存储 前端开发 安全
|
10天前
|
前端开发 JavaScript 数据格式
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
17 1
|
10天前
|
前端开发
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
11 0
|
10天前
react18【系列实用教程】useReducer —— 升级版的 useState (2024最新版)
react18【系列实用教程】useReducer —— 升级版的 useState (2024最新版)
9 0
|
10天前
|
前端开发 JavaScript
react18【系列实用教程】useState —— 声明响应式变量(2024最新版)含useState 的异步更新机制,更新的合并,函数传参获取更新值,不同版本异步更新差异,更新对象和数组
react18【系列实用教程】useState —— 声明响应式变量(2024最新版)含useState 的异步更新机制,更新的合并,函数传参获取更新值,不同版本异步更新差异,更新对象和数组
14 0
|
1月前
|
前端开发
前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步还是异步?
前端React篇之React setState 调用的原理、React setState 调用之后发生了什么?是同步还是异步?
|
2月前
|
前端开发
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
React Hooks - useState 的使用方法和注意事项(1),web前端开发前景
|
2月前
|
前端开发 JavaScript
React中的状态管理:useState与useReducer的使用与探讨
【4月更文挑战第25天】本文探讨了React中构建动态界面的关键——状态管理,重点关注`useState`和`useReducer` Hook。`useState`适用于简单状态管理,例如计数器,而`useReducer`在处理复杂逻辑和多个状态更新时更具优势,提供更好的组织和可维护性。选择使用哪个取决于状态逻辑复杂度、可维护性和性能需求。合理运用这两个工具能实现高效、可维护的React应用。
|
2月前
|
存储 前端开发 安全
深入理解React中的useState:函数组件状态管理的利器
深入理解React中的useState:函数组件状态管理的利器
|
2月前
|
存储 前端开发 JavaScript
React Hooks实战:从useState到useContext深度解析
React Hooks 深度解析:useState用于函数组件的状态管理,通过初始化和更新状态实现渲染控制;useContext则提供跨组件数据传递。useState的状态更新是异步的,不支持浅比较,可结合useEffect处理副作用。useContext在多层组件间共享状态,但可能导致不必要的渲染。两者结合可创建复杂应用场景,如带主题切换的计数器。了解其工作原理和优化策略,能有效提升React应用性能。
45 0