参考内容:
第十一篇:setState 到底是同步的,还是异步的?
彻底搞懂setState到底是同步还是异步(一)
1、在react可调度范围内的setState是
异步
的,并且多次setState会
合并
只执行最后一次,进行批量更新。
-
react合成事件
-
生命周期
-
事件处理,如onClick、onChange等。
2、在react可调度范围外的setState是
同步
的。
-
宏任务 setTimeout、setInterval
-
微任务 .then
-
原生 js 绑定事件 document.addEventListener()
3、setState 并非真正的异步,而是通过设置全局变量
isBatchingUpdates
来判断 setState是先存进队列还是直接更新,如果值为true,则执行异步操作,如果为false,则直接更新。
isBatchingUpdates
会在 React 可以控制的地方,则为true,比如React生命周期和合成事件中,而在外部 react 无法控制的地方,比如原生事件,具体就是在 addEventListener、setTimeout、setInterval 、.then等事件中,就只能同步。
4、一般认为,做异步设计是为了性能优化,减少渲染次数。React团队的观点是:
-
保持内部一致性。如果将 state 改为同步更新,那尽管 state 的更新是同步的,但是 props不是。
-
启用并发更新,完成异步渲染。
import React from "react";
export default class App extends React.Component{
state = {
count: 0
increment = () => {
console.log('increment setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
console.log('increment setState后的count', this.state.count)
}
triple = () => {
console.log('triple setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
console.log('triple setState后的count', this.state.count)
}
reduce = () => {
setTimeout(() => {
console.log('reduce setState前的count', this.state.count)
this.setState({
count: this.state.count - 1
});
console.log('reduce setState后的count', this.state.count)
},0);
render(){
return <div>
<button onClick={this.increment}>点我增加</button>
<button onClick={this.triple}>点我增加三倍</button>
<button onClick={this.reduce}>点我减少</button>
</div>
接着我把组件挂载到 DOM 上:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
此时浏览器里渲染出来的是如下图所示的三个按钮:
此时有个问题,若从左到右依次点击每个按钮,控制台的输出会是什么样的?
参考内容:彻底搞懂setState到底是同步还是异步(二)
React18版本引入了自动批处理功能,批处理是指,当 React 在一个单独的重渲染事件中批量处理多个状态更新以此实现优化性能。如果没有自动批处理的话,我们仅能够在 React 事件处理程序中批量更新。在 React 18 之前,默认情况下 promise、setTimeout、原生应用的事件处理程序以及任何其他事件中的更新都不会被批量处理;但现在,这些更新内容都会被自动批处理。
1、在react18中setTimeout里的setState也是异步的批量处理了,都是setTimeout 中的批处理明显落后外部的批处理。
上面代码在react18中的结果:0 0 1 1 2 2.
import React from 'react';
import './App.css';
class AppClass extends React.Component {
state = {
count: 0,
handleClick = () => {
this.setState({ count: 1 });
console.log('count: ', this.state.count);
this.setState({ count: 2 });
console.log('count: ', this.state.count);
setTimeout(() => {
this.setState({ count: 3 });
console.log('count: ', this.state.count);
this.setState({ count: 4 });
console.log('count: ', this.state.count);
}, 0);
render() {
return (
<div className='App'>
<button onClick={this.handleClick}>count = {this.state.count}</button>
</div>
export default AppClass;
react 18的控制台打印结果:
react 17的打印结果:
对比下结果:
- 前两次的结果相同,都是0,证明这块是跟 v17 中一样的,都是异步
- 后两次结果不一样,v17中是同步更新的,所以每次setState 后都可以立即获取到更新后的值,但v18 中打印的是两个2 ,说明是异步更新的,只是这个异步更新跟setTimeout 外部的不在一个批中,setTimeout 中的批处理明显落后外部的批处理。
页面会渲染几次? (执行setState会重新渲染页面)
- react17中在setTimeout外会合并渲染一次,在setTimeout中是同步的,会渲染两次,总共页面会渲染三次。
- react18中在setTimeout外会合并渲染一次,在setTimeout中式异步的,进行自动批处理,会渲染一次,总共页面会渲染两次。
参考内容:彻底搞懂setState到底是同步还是异步(三)
函数式组件中使用hooks的useState。
1、react 17:由于闭包输出的内容全部是count初始值。在setTimeout外部是异步批处理,在setTimeout内部不是批处理,与react 17中的类组件是否批处理一样。
2、react 18:由于闭包输出的内容全部是count初始值。在setTimeout外部是异步批处理,在setTimeout内部也是自动批处理,与react 18中的类组件是否批处理一样。
3、案例
把上述题目改造成hooks的形式:
import { useState } from 'react';
import './App.css';
function App() {
const [count, setCount] = useState(0);
const handle = () => {
setCount(1);
console.log('count: ', count);
setCount(2);
console.log('count: ', count);
setTimeout(() => {
setCount(3);
console.log('count: ', count);
setCount(4);
console.log('count: ', count);
}, 0);
return (
<div className='App'>
<button onClick={handle}>count is {count}</button>
</div>
export default App;
react 17的执行结果:
react 18的执行结果:
输出结果都是0,看似都是在批处理,为了能更清楚的看到 React 的渲染行为,修改下上边的代码,在每次渲染都都打印下当前的 count值,添加如下代码:
useEffect(() => {
console.log('render: 此时的count: ', count);
});
再次查看输出结果:
- 在 React 17 下,render 打印了 3 次,说明组件重新渲染了 3 次,回顾下当时讲解 React 17 的 setState时,setTimeout外是批处理重新渲染一次,setTimeout中是同步渲染,重新渲染两次,能对上。
- 再来看下 React 18 下,render 打印了 2 次,说明组件重新渲染了 2 次,在对比下前面讲解的 React18 的自动批处理功能,setTimeout外部批处理一次,内部批处理一次,重新渲染两次,也能对上.
- 那为什么这期间打印的 count 都是 0 呢?
这是由于js的特性闭包导致的,闭包就是内部函数可以访问外部函数的变量。
组件App是一个函数,handle 是 App内的函数,这也就形成了一个闭包,所以 handle 函数才能访问到 count 变量的值。
无论延时多长时间,最终打印的count值一直是旧值,也就是 0。
要是想要立即用更新后的state,可以使用setState的第二个函数。
在工作中或者面试时都会碰到 setState 的一些问题。今天我们就这些问题来看下 setState。
用过 react 的都知道,修改 state 唯一的办法就是 setState方法,而不是直接修改state值。
而在 react 中 setState 的执行是"异步"的,是"批量"的,而不是"同步"的。
这是因为在调用 setState 之后 react 并没有立即更新,而是缓存起来了,等事件完成之后,在进行批量更新,一次更新之后并重新渲染,避免多次渲染组件。
接下来看下在那种情况为同步?那种情况.
在使用state的时候, 如果我们企图直接修改state中的某一个值之后直接打印(使用)他,就会发现,他其实并没有改变。
就像下面的例子,企图通过点击事件之后就使用修改之后的state的值,但是会发state中的并没有被立即修改,还是原先的值,我们都知道那是因为 setState就相当于是一个异步操作,不能立即被修改。
import React, { Component } from 'react';
export default class App extends Component {
setState是React中使用频率最高的一个API(当然hooks出现之前),它的用法灵活多样,并且也是React面试题经常会考的一个知识点。在这篇文章中,我对React的setState进行了很多解析,希望可以帮助大家真正理解setState。(其中涉及到一个源码,我有贴出,但是没有详细展开,有机会我们再对源码进行解析,大家不是很懂也不影响你的学习,只需要知道React内部是这样做...
1. setState同步?异步?
在 React 的类式组件中,我们可以使用setState方法更新state状态。但有些时候使用setState之后,得不到最新的数据。
其实 React 中setState本身执行的过程和代码是同步的,只是因为 React 框架本身的性能优化机制而导致的。React 中合成事件和生命周期函数的调用顺序在更新之前,导致在合成事件和生命周期函数中无法立刻得到更新后的值,形成了异步的形式。
假如在一个合成事件中,循环调用了setState方法n次,如果 React 没有优化,
作者:Nealyang转发链接:https://mp.weixin.qq.com/s/fpV4N1Nre6M1WTSdnFJDcg前言React源码分析与实现(一):组件的初始化与渲染「实践篇」React源码分析与实现(三):实践 DOM Diff状态更新此次分析setState基于0.3版本,实现比较简单,后续会再分析目前使用的版本以及事务机制。流程图大概如下IMAGEsetState的源码比较...
其实 博主之前是搞vue开发的,后来就转了react,现在全职搞react也有两个月了, 之前面试的时候碰见一个 有趣的问题,
面试官问 如何在react中实现同步的写法,我有点疑惑了,说实话刚转过来,对react并不是太熟悉 有的功能也没有完全摸透。
我就实话说说了,这个还真没有用过呢, 麻烦老哥稍微提点一下,那个面试官就说了,在某些原生js事件种,或者 setTimeout 和 setIntrval中可以获取到 最新的state状态 达到同步的效果。
这里 我们来尝试看下 效果哈
拿类式组件 来做例子
接受一个回调函数作为第二个参数,在状态更新完成并且组件重新渲染后,会调用这个回调函数。这样可以确保在回调函数中使用的数据是最新的。的第二个参数,它是一个回调函数,会在状态更新并且重新渲染完成后被调用。这样就可以确保在回调函数中使用的状态是最新的。方法接受了一个回调函数作为第二个参数。方法接受了一个回调函数作为第二个参数。会调用这个回调函数。这样在回调函数中使用的。中的数据并不能保证能获取到最新的数据,因为。会调用这个回调函数。这样在回调函数中使用的。完成后再执行某个函数并使用。是一个异步操作,因此在。
executionContext代表了react当前的调度状态, 如果退出了react的调度这个值就会重新变成NoContext. 也就是说, 如果你调用setState的时候并不处于react的调度状态中, 那么就会同步的去执行你的setState.这也是为什么一旦我们使用一些异步操作就会导致setState变成同步的原因, 而在react 18中这段代码变成了这样。在编写UI测试时,可以将渲染、用户事件或数据获取等任务视为与用户界面交互的“单元”。