React之setState的前世今生

目录

  • React的setState的前世今生
    • 概述
    • setState的三种用法
    • setState异步函数

React的setState的前世今生

setState是修改state的一个函数,在修改后会自动调用render函数重新渲染DOM,那么你知道它有多少种用法、各写法直接有什么区别和好处、为什么在React18是异步而不是同步、哪种情况下它会变成同步呢?接下来我将一一解惑。
以下javascript代码均是在handleChangeCount中被执行。

  • Test.jsx
import React, { Component } from "react";

export class Test extends Component {
  constructor(props) {
    super();

    this.state = {
      count: 0,
    };
  }

  handleChangeCount = () => {
	// ...
  };

  render() {
    const { count } = this.state;

    return (
      <div>
        <h1>{count}</h1>
        <button onClick={this.handleChangeCount}>count + 1</button>
      </div>
    );
  }
}

export default Test;

概述

Vue 3 的渲染机制和 React 18 中的 setState 机制在某些方面有相似之处,但也有一些区别。简单来说,Vue中是使用数据劫持,订阅发布,数据变化就更新视图。React中是主动调用setState函数才会更新视图。

  1. 异步更新: React 18 中的 setState 机制使用了异步更新,即当调用 setState 时,React 并不会立即更新组件,而是将更新放入队列中,在合适的时机批量更新,这样可以提高性能。
    Vue 3 中的渲染机制也是异步更新的,但是它使用的是响应式系统,当响应式数据发生变化时,Vue 会将更新放入微任务队列中,在下一个事件循环中执行更新,这也能够提高性能。

  2. 调度器:React 18 引入了新的调度器(Scheduler)机制,可以让 React 更加灵活地控制更新的优先级和时机,以提高性能和用户体验。
    Vue 3 中并没有像 React 18 中那样明确的调度器概念,但是其使用的异步更新机制也可以让开发者在一定程度上控制更新的时机。
    更新策略:

  3. 在 React 中,更新是基于组件的状态变化而触发的,而且由于状态是不可变的,React 可以更容易地进行优化,例如使用浅比较来决定是否需要重新渲染组件。
    在 Vue 中,更新是基于响应式数据的变化触发的,Vue 会使用更复杂的策略来决定是否需要重新渲染组件,包括对模板和虚拟 DOM 的分析等。

setState的三种用法

  • 用法一:使用setState传入对象修改count
this.setState({
  count: this.state.count + 1,
});
  • 用法二: 使用回调函数修改count。

以函数回调形式修改有两点好处:

  1. 能够使对state修改的操作代码高内聚,集中在setState回调函数中。
  2. 能够获取到更新前的state和props,做更多的事情。
this.setState((prevState, prevProps) => {
  // 更多的处理...
  return {
	count: prevState.count + 1,
  }
})
  • 用法三:setState是一个异步函数,不会阻塞同步代码。如果你需要使用到最新的state,可以使用第二个参数——更新后的回调。
this.setState(
  {
	count: this.state.count + 1,
  },
  () => {
	console.log("更新后:" + this.state.count); // 输出:1
  }
);
console.log("更新前:" + this.state.count); // 输出:0

setState异步函数

React18中被修改为自动异步操作,所以会将setState的三次操作加入队列中,批量处理,只做一次更新。当然也提供了同步的方式,使用官方提供的API——flushSync包裹食用。

问题:为什么是异步,而不是同步?更详细的解答

答:从性能上,以下三次setState可以想象出,如果是同步,React将依次执行三次Diffrender。如果是异步,那么React执行阈值为1,极大提升了性能,只会有一次Diffrender。而往往在实际开发场景中,数据有先后顺序且要实时更新时,就会出现很多地方调用setState

从安全上,如果是同步,且count是作为props传递给了子组件,在第一个setState执行后,已经改变了父组件中的count值,接着执行第二个setState,但是未进行render渲染DOM,所以子组件中的props.count还是原来的值,子组件视图是没有更新的,这将导致statepropsrefs之间数据的不一致性。

诶,这时候又会有聪明的同学问了:难道我三次操作变成一次不会导致延迟渲染视图吗?

所以React18 引入了新的调度器(Scheduler)机制,可以让 React 更加灵活地控制更新的优先级和时机,以提高性能和用户体验。

  • 多次调用,批处理setState
 this.setState((prevState) => {
  console.log(this.state.count, prevState.count)  // 0, 0
  return {
	count: prevState.count + 1,
  };
});
this.setState((prevState) => {
  console.log(this.state.count, prevState.count) // 0, 1
  return {
	count: prevState.count + 1,
  };
});
this.setState((prevState) => {
  console.log(this.state.count, prevState.count) // 0, 2
  return {
	count: prevState.count + 1,
  };
});

console.log(this.state.count) // 3
  • React18中的setState同步
flushSync(() => {
  // flushSync函数内部仍然是异步状态
  this.setState({count: this.state.count + 1});
});
console.log(this.state.count);  // 1
  • React18之前的同步setState复现

以下代码使用的React17,作为测试复现setState的同步情况版本。

问题:为什么用setTimeout包裹就是同步,不用就是异步?

答:在类组件中的所有函数,都将委托React调用。所以如果不用setTimeout包裹,那么add函数中的setState将被加入队列中,进行批量处理,异步更新。可一但被setTimeout包裹,表示被添加到浏览器的宏任务队列中(Event Loop),那么将是浏览器调用setTimeout的箭头函数,执行宏任务队列,setState也将不会走异步逻辑。像这种情况的还有使用原生DOM的回调、微任务队列。更多情况

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script src="https://cdn.bootcdn.net/ajax/libs/react/17.0.2/umd/react.development.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/react-dom/17.0.2/umd/react-dom.development.min.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
  <body>
    <div id="app"></div>

    <script type="text/babel">
      class App extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            count: 0,
          };
        }
	    componentDidMount() {
		  // 同步
          const addBtnEl = document.getElementById("addBtn");
          addBtnEl.addEventListener("click", () => {
            this.setState((prevState) => {
              return {
                count: prevState.count + 1,
              };
            });
            console.log(this.state.count);
          });
        }

        add() {
		  // 异步
		  this.setState((prevState) => {
			  return {
				count: prevState.count + 1,
			  };
			});
		  console.log(this.state.count);
		
		  // 同步
          setTimeout(() => {
            this.setState((prevState) => {
              return {
                count: prevState.count + 1,
              };
            });
            console.log(this.state.count);
          }, 0);
	    }
		        

        render() {
          return (
            <div>
              <h2>{this.state.count}</h2>
              <button id="addBtn" onClick={() => this.add()}>+1</button>
            </div>
          );
        }
      }

      ReactDOM.render(<App />, document.getElementById("app"));
    </script>
  </body>
</html>

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值