React中setState同步异步场景的使用

目录
  • setState同步异步场景
  • 描述
  • 原理
    • 保证内部数据统一
    • 启用并发更新
  • 参考

setState同步异步场景

React通过this.state来访问state,通过this.setState()方法来更新state,当this.setState()方法被调用的时候,React会重新调用render方法来重新渲染UI。相比较于在使用Hooks完成组件下所需要的心智负担,setState就是在使用class完成组件下所需要的心智负担,当然所谓的心智负担也许叫做所必须的基础知识更加合适一些。

描述

setState只在合成事件和生命周期钩子函数中是异步的,而在原生事件中都是同步的,简单实现一个React Class TS例子。

import React from "react";
import "./styles.css";

interface Props{}
interface State{
  count1: number;
  count2: number;
  count3: number;
}
export default class App extends React.Component<Props, State>{
  constructor(props: Props){
    super(props);
    this.state = {
      count1: 0,
      count2: 0,
      count3: 0
    }
  }

  incrementAsync = () => {
    console.log("incrementAsync before setState", this.state.count1);
    this.setState({
      count1: this.state.count1 + 1
    });
    console.log("incrementAsync after setState", this.state.count1);
  }
  incrementSync = () => {
    setTimeout(() => {
      console.log("incrementSync before setState", this.state.count2);
      this.setState({
        count2: this.state.count2 + 1
      });
      console.log("incrementSync after setState", this.state.count2);
    },0);
  }
  incrementAsyncFn = () => {
    console.log("incrementAsyncFn before setState", this.state.count3);
    this.setState({
        count3: this.state.count3 + 1
        },
        () => {
            console.log("incrementAsyncFn after.1 setState", this.state.count3);
        }
    );
    this.setState(state => {
      console.log("incrementAsyncFn after.2 setState", state.count3);
      return {count3: state.count3}
    });
  }
  render(){
    return <div>
      <button onClick={this.incrementAsync}>异步</button>
      <button onClick={this.incrementSync}>同步</button>
      <button onClick={this.incrementAsyncFn}>异步函数参数</button>
    </div>
  }
}

以此点击三个按钮的话,可以得到以下输出。

incrementAsync before setState 0
incrementAsync after setState 0
incrementSync before setState 0
incrementSync after setState 1
incrementAsyncFn before setState 0
incrementAsyncFn after.2 setState 1
incrementAsyncFn after.1 setState 1

首先看incrementAsync的结果,在这里我们可以看出,在合成事件调用setState之后,this.state是无法立即得到最新的值。
对于incrementSync的结果,在非合成事件的调用时,this.state是可以立即得到最新的值的,例如使用addEventListenersetTimeoutsetInterval等。
对于incrementAsyncFn的两个结果,首先来说after.2的结果,对于this.state也是可以得到最新的值的,如果你需要基于当前的state来计算出新的值,那么就可以通过传递一个函数,而不是一个对象的方式来实现,因为setState的调用是分批的,所以通过传递函数可以链式地进行更新,当然前提是需要确保它们是一个建立在另一个之上的,也就是说传递函数的setState的值是依赖于上次一的SetState的,对于after.1的结果,setState函数的第二个参数是一个回调函数,在setState批量更新完成后可以拿到最新的值,而after.2也是属于批量更新需要调用的函数,所以after.1会在after.2后执行。

原理

React将其实现为异步的动机主要是性能的考量,setState的异步并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和生命周期钩子函数的调用顺序在批处理更新之前,导致在合成事件和生命周期钩子函数中没法立马拿到更新后的值,形式了所谓的异步,实际上是否进行批处理是由其内部的isBatchingUpdates的值来决定的。
setState依赖于合成事件,合成事件指的是React并不是将click等事件直接绑定在DOM上面,而是采用事件冒泡的形式冒泡到顶层DOM上,类似于事件委托,然后React将事件封装给正式的函数处理运行和处理。说完了合成事件再回到setStatesetState的批量更新优化也是建立在合成事件上的,其会将所有的setState进行批处理,如果对同一个值进行多次 setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时也会对其进行合并批量更新,而在原生事件中,值会立即进行更新。
采用批量更新,简单来说就是为了提升性能,因为不采用批量更新,在每次更新数据都会对组件进行重新渲染,举个例子,让我们在一个方法内重复更新一个值。

this.setState({ msg: 1 });
this.setState({ msg: 2 });
this.setState({ msg: 3 });

事实上,我们真正想要的其实只是最后一次更新而已,也就是说前三次更新都是可以省略的,我们只需要等所有状态都修改好了之后再进行渲染就可以减少一些性能损耗。还有一个例子,如果更改了N个状态,其实只需要一次setState就可以将DOM更新到最新,如果我们更新多个值。

this.setState({ msg: 1 });
this.setState({ age: 2 });
this.setState({ name: 3 });

此处我们分三次修改了三种状态,但其实React只需要渲染一次,在setState批处理之后会将其合并,并进行一次re-render就可以将整个组件的DOM更新到最新,根本不需要关心这个setState到底是从哪个具体的状态发出来的。

那么还有一个问题,首先我们可以认同进行批处理更新对我们的性能是有益的,例如ChildParent都调用setState,我们不需要重新渲染Child两次。但是此时我们可能会想到一个问题,为什么不能如同Vue一样,Vue是在值更新之后触发setter然后进行更新,更新的过程同样也是采用异步渲染,也会将所有触发Watcherupdate进行去重合并再去更新视图,也就是说Vue是立即修改了值,而后再去更新视图的。也就是说,相比较于React,为什么不能在同样做批处理的情况下,立即将setState更新写入this.state而不等待协调结束。

任何一种解决方案都有权衡,对于Vue来说因为其是通过劫持了数据的setter过程,在使用的也是直接使用=直接赋值的,而在赋值之后进行视图的更新也是一个自然的过程,如果类似于React一样在=之后这个值依然没有变化,在直觉上是非常不符合常理的,虽然Vue是通过劫持setter实现的视图更新,但是做到如同React一样并不是不可能的,这是Vue采用的解决方案上的权衡,当然这只是可能的一个理由,说是问题的权衡,实际上还是需要对比React来看,对于React中要处理的问题,Vue自然会有自己解决方案的权衡,归根到底还是框架的设计哲学的问题。对于上面提出的在同样做批处理的情况下,立即将setState更新写入this.state而不等待协调结束的这个问题,dan给予了两个理由,在此简作总结,完整的英文版本还请看参考中的github issue

保证内部数据统一

即使state是同步更新的,但props是不会的,在重新渲染父组件之前,无法知道props,如果同步执行此操作,批处理就会消失。现在React提供的对象statepropsrefs在内部是一致的。这意味着如果只使用这些对象,则可以保证它们引用完全协调的树,即使它是该树的旧版本。当仅使用state时,同步刷新的模式将起作用。

console.log(this.state.value); // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 2

但是,假设需要提升此状态以在几个组件之间共享,因此将其移动到父级,也就是说有props参与到了传值,那么同步setState模式就会有问题,此时将state提升到了父组件,利用props将值传导子组件。

console.log(this.props.value); // 0
this.props.onIncrement();
console.log(this.props.value); // 0
this.props.onIncrement();
console.log(this.props.value); // 0

这是因为在这个解决方案中,this.state会立即刷新,而this.props不会,而且我们不能在不重新渲染父对象的情况下立即刷新this.props,这意味着我们将不得不放弃批处理的策略。还有更微妙的情况说明这如何破坏一致性的,例如这种方案正在混合来自props尚未刷新和state建议立即刷新的数据以创建新状态。在React中,this.statethis.props都只在协调和刷新之后更新,所以你会在refactoring之前和之后看到0被打印出来。这使得提升状态安全。在某些情况下这可能会带来不便,特别是对于来自更多OO背景的人来说,他们只想多次改变状态,而不是考虑如何在一个地方表示完整的状态更新,我可以理解这一点,尽管我确实认为从调试的角度来看,保持状态更新的集中更加清晰。总而言之,React模型并不总是会产生最简洁的代码,但它在内部是一致的,并确保提升状态是安全的。

启用并发更新

从概念上讲React的行为就好像每个组件都有一个更新队列,我们在这里讨论是否同步刷新state有一个前提那就是我们默认更新节点是遵循特定的顺序的,但是按默认顺序更新组件在以后的react中可能就变了。对于现在我们一直在谈论的异步渲染,我承认我们在传达这意味着什么方面做得不是很好,但这就是研发的本质:你追求一个在概念上看起来很有前途的想法,但只有在花了足够的时间之后才能真正理解它的含义。
对于这个理由,是React发展的一个方向。我们一直在解释异步渲染的一种方式是React可以根据setState()调用的来源分配不同的优先级:事件处理程序、网络响应、动画等。例如你现在正在打字,那么TextBox组件需要实时的刷新,但是当你在输入的时候,来了一个信息,这个时候可能让信息的渲染延迟到某个阈值,而不是因为阻塞线程而让输入卡顿。如果我们让某些更新具有较低优先级,我们可以将它们的渲染分成几毫秒的小块,这样用户就不会注意到它们。异步rendering不仅仅是性能上的优化,我们认为这是React组件模型可以做什么的根本性转变。例如,考虑从一个屏幕导航到另一个屏幕的情况,通常会在渲染新屏幕时显示一个导航器,但是如果导航速度足够快,闪烁并立即隐藏导航器会导致用户体验下降,更糟糕的是如果有多个级别的组件具有不同的异步依赖项例如数据、代码、图像等,您最终会得到一连串短暂闪烁的导航器。由于所有的DOM重排,这既在视觉上令人不快,又使您的应用程序在实践中变慢。如果当您执行一个简单的setState()来呈现不同的视图时,我们可以开始在后台呈现更新后的视图。如果您自己不编写任何协调代码,您可以选择在更新时间超过某个阈值时显示导航器,否则当整个新子树的异步依赖项是时让React执行无缝转换使满意。请注意,这只是可能的,因为this.state不会立即刷新,如果它被立即刷新,我们将无法开始在后台渲染视图的新版本,而旧版本仍然可见且可交互,他们独立的状态更新会发生冲突。

参考

https://www.jianshu.com/p/cc12e9a8052c
https://juejin.cn/post/6844903636749778958
https://zh-hans.reactjs.org/docs/faq-state.html
https://blog.csdn.net/zz_jesse/article/details/118282921
https://blog.csdn.net/weixin_44874595/article/details/104270057

到此这篇关于React中setState同步异步场景的使用的文章就介绍到这了,更多相关React setState同步异步内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 代码解析React中setState同步和异步问题

    React起源于Facebook的内部项目.React的出现是革命性的创新,React的是一个颠覆式的前端框架.在React官方这样介绍的它:一个声明式.高效.灵活的.创建用户界面的JavaScript库,即使React的主要作用是构建UI,但是项目的逐渐成长已经使得react成为前后端通吃的WebApp解决方案. angular中用的是watcher对象,vue是观察者模式,react就是state了,他们各有各的特点,没有好坏之分,只有需求不同而选择不同. React的官方网址:https:

  • 关于React中setState同步或异步问题的理解

    目录 1. setState同步?异步? 2. 表现为异步 1. React 合成事件 2. 生命周期函数 3. 表现为同步 1. 原生事件 2. setTimeout 4. setState的第二个参数 1. setState同步?异步? 在 React 的类式组件中,我们可以使用setState方法更新state状态.但有些时候使用setState之后,得不到最新的数据. 其实 React 中setState本身执行的过程和代码是同步的,只是因为 React 框架本身的性能优化机制而导致的.

  • React中setState的使用与同步异步的使用

    在react中,修改状态如果直接使用this.state,不会引起组件的重新渲染,需要通过 this.setState来对组件的属性进行修改. 1.this.setState的两种定义方式 定义初始状态 state = { count: 0 }, 如果此时有一个按钮,点击按钮让计数加1,我们可以有两种写法 (1)传递对象 this.setState({ count: this.state.count + 1}) (2)传递函数 this.setState((state, props) => ({

  • React中setState同步异步场景的使用

    目录 setState同步异步场景 描述 原理 保证内部数据统一 启用并发更新 参考 setState同步异步场景 React通过this.state来访问state,通过this.setState()方法来更新state,当this.setState()方法被调用的时候,React会重新调用render方法来重新渲染UI.相比较于在使用Hooks完成组件下所需要的心智负担,setState就是在使用class完成组件下所需要的心智负担,当然所谓的心智负担也许叫做所必须的基础知识更加合适一些.

  • 详细谈谈React中setState是一个宏任务还是微任务

    目录 前言 面试官的问法是否正确?§ React 是如何控制 setState 的 ?§ 未来会有异步的 setState§ 总结 前言 最近有个朋友面试,面试官问了个奇葩的问题,也就是我写在标题上的这个问题. 能问出这个问题,面试官应该对 React 不是很了解,也是可能是看到面试者简历里面有写过自己熟悉 React,面试官想通过这个问题来判断面试者是不是真的熟悉 React

  • 解决react中useState状态异步更新的问题

    目录 疑惑 状态异步更新带来的问题 问题示例 问题解决 类组件的解决方案 函数组件的解决方案 其他解决方案 结尾 疑惑 相信刚开始使用react函数组件的小伙伴也遇到过一个坑,就是 useState 更新状态是异步更新的,但是react 并没有提供关于这个问题的解决方案.那我们能否使用自己的方法来解决这个问题呢?答案肯定是可以的. 状态异步更新带来的问题 就拿一个比较常见的场景来说.在react项目中,我们想在关闭对话框后再去处理其他业务.但是 useState 的状态是异步更新的.我们通过se

  • React 中 setState 的异步操作案例详解

    目录 前言 React 中的 setState 为什么需要异步操作? 什么时候setState会进行同步操作? 前言 在使用state的时候, 如果我们企图直接修改state中的某一个值之后直接打印(使用)他,就会发现,他其实并没有改变. 就像下面的例子,企图通过点击事件之后就使用修改之后的state的值,但是会发state中的并没有被立即修改,还是原先的值,我们都知道那是因为 setState就相当于是一个异步操作,不能立即被修改. import React, { Component } fr

  • React中useLayoutEffect钩子使用场景详解

    目录 简介 useEffect钩子的概述 钩子流程 useLayoutEffect钩子的概述 钩子流程 什么时候使用useLayoutEffect钩子? 总结 简介 不久前,React对其功能组件进行了一次重大更新(在2019年3月的16.8版本中),终于为这些组件提供了一种变得有状态的方法. 钩子的加入不仅意味着功能组件将能够提供自己的状态,而且还能通过引入useEffect钩子来管理自己的生命周期事件. 此外,这次更新还引入了一个全新的useLayoutEffect钩子,根据React文档,

  • React中的Context应用场景分析

    Context定义和目的 Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的逐层传递 props. 应用场景 哪些数据会需要共享? Context 设计目的是为了共享那些对于一个组件树而言是**"全局"的数据**,例如当前认证的用户.主题或首选语言. 使用步骤 1. 创建并初始化Context const MyContext = createContex(defaultValue); 创建一个 Context 对象.当 React 渲染一个订阅了这个 Cont

  • 深入研究React中setState源码

    React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定.修改数据的同时,可以实现View的刷新.这大大简化了我们的逻辑,只用关心数据流的变化,同时减少了代码量,使得后期维护也更加方便.这个特性则要归功于setState()方法.React中利用队列机制来管理state,避免了很多重复的View刷新.下面我们来从源码角度探寻下setState机制. 1 还是先声明一个组件,从最开始一步步来寻源: class App extends Comp

随机推荐