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

相关文章
|
5月前
|
前端开发 JavaScript CDN
React 在 html 中 CDN 引入(包含 useState、antd、axios ....)
React 在 html 中 CDN 引入(包含 useState、antd、axios ....)
120 0
|
29天前
|
前端开发 数据处理 开发者
React的useState:开启组件状态管理的新篇章
React的useState:开启组件状态管理的新篇章
|
3月前
|
前端开发 JavaScript 测试技术
React Hooks之useState、useRef
React Hooks之useState、useRef
|
3月前
|
存储 前端开发 JavaScript
React Hooks的useState、useRef使用
React Hooks的useState、useRef使用
23 2
|
4月前
|
前端开发 JavaScript
React 钩子:useState()
React 钩子:useState()
41 0
React 钩子:useState()
|
4月前
|
前端开发
说说React中setState和replaceState的区别?
在 React 中,setState()和 replaceState()是用于更新组件状态的两个方法。它们之间有一些区别
20 0
|
4月前
|
前端开发 算法
React中的setState执行机制?
React中的setState执行机制?
|
4月前
|
前端开发
React中setState方法详细讲解
React中setState方法详细讲解
|
5月前
|
前端开发 CDN
React 在 html 中 CDN 引入(包含useState、antd、axios ....)
React 在 html 中 CDN 引入(包含useState、antd、axios ....)
63 0
|
5月前
|
前端开发 JavaScript
react中的useState为什么是一个数组,而不是一个对象?
react中的useState为什么是一个数组,而不是一个对象?
41 0