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后,广东靓仔建议大家做个小结,顺便举一反三,顺便把某原理梳理梳理。下次就能很好的避免这些问题。

相关文章
|
2月前
|
前端开发 JavaScript
深入理解并实践React Hooks —— useEffect与useState
深入理解并实践React Hooks —— useEffect与useState
140 1
|
2月前
|
前端开发 JavaScript
学习react基础(3)_setState、state、jsx、使用ref的几种形式
本文探讨了React中this.setState和this.state的区别,以及React的核心概念,包括核心库的使用、JSX语法、类与函数组件的区别、事件处理和ref的使用。
63 3
学习react基础(3)_setState、state、jsx、使用ref的几种形式
|
24天前
|
存储 前端开发 JavaScript
React useState 和 useRef 的区别
本文介绍了 React 中 `useState` 和 `useRef` 这两个重要 Hook 的区别和使用场景。`useState` 用于管理状态并在状态变化时重新渲染组件,适用于表单输入、显示/隐藏组件、动态样式等场景。`useRef` 则用于在渲染之间保持可变值而不触发重新渲染,适用于访问 DOM 元素、存储定时器 ID 等场景。文章还提供了具体的代码示例,帮助读者更好地理解和应用这两个 Hook。
33 0
|
2月前
|
前端开发
React中函数式Hooks之useState的使用
本文介绍了React中函数式组件的Hooks——`useState`的使用方法。`useState`允许在函数式组件中使用状态,它返回一个数组,其中包含当前状态的值和更新该状态的函数。文章通过示例代码展示了如何声明状态变量和更新状态变量,包括对数值和对象状态的更新。此外,还展示了如何通过点击按钮触发状态更新,实现交互功能。
36 1
|
3月前
|
前端开发 开发者
介绍React的useState
【8月更文挑战第6天】 介绍React的useState
39 1
|
4月前
|
存储 前端开发 安全
|
4月前
|
前端开发 JavaScript 数据格式
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
react18【系列实用教程】Hooks (useState,useReducer,useRef,useEffect,useContext,useMemo,useCallback,自定义 Hook )
89 1
|
4月前
|
前端开发
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
react18【系列实用教程】Hooks 闭包陷阱 (2024最新版)含useState 闭包陷阱,useEffect 闭包陷阱,useCallback 闭包陷阱
69 0
|
4月前
react18【系列实用教程】useReducer —— 升级版的 useState (2024最新版)
react18【系列实用教程】useReducer —— 升级版的 useState (2024最新版)
50 0
|
4月前
|
前端开发 JavaScript
react18【系列实用教程】useState —— 声明响应式变量(2024最新版)含useState 的异步更新机制,更新的合并,函数传参获取更新值,不同版本异步更新差异,更新对象和数组
react18【系列实用教程】useState —— 声明响应式变量(2024最新版)含useState 的异步更新机制,更新的合并,函数传参获取更新值,不同版本异步更新差异,更新对象和数组
224 0
下一篇
无影云桌面