本文作者: 小炒芝士
setState的特点
setState(updater, [callback]) // class写法
const [value, setValue] = useState(init) // hook写法
复制代码
- setState/setValue将最新的state排入队列,并通知React有更新,需要重新渲染父组件和子组件
- 以上两种方式导致的state变更,并不会及时(同步)更新组件,而是批量、异步更新组件,所以,在调用setState(setValue同)后,有很大可能无法获取到最新的状态值
- 针对2中问题,如果使用setState,可以在callback或者componentDidUpdate中执行state变更后的处理,如果使用useState,则可以在useEffect中订阅状态的变更,做相应处理
注意:setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
为什么setState 会批处理,什么时候会批处理?
为什么要批处理
我们考虑一下,以React的使用者的角度来看,如果每一次state更新都会导致组件的重新渲染的话,对性能有很大的影响。所以批量处理state的更新是必然的。
从React的开发者的角度,如果每一次都更新
- 这样会破坏掉 props 和 state 之间的一致性,造成一些难以 debug 的问题。
- 这样会让一些我们正在实现的新功能变得无法实现。
何时会批处理
在React16版本及以前,React 会对所有React内部触发的事件监听函数中的更新(比如onClick函数)做批处理,如果是绕过react组件,如addEventListenr,或者异步调用如异步请求或者setTimeout等,不会进行批处理
在React17版本及之后,React会对所有的更新做批处理
如果我们想要对非react内部触发函数进行批处理,如下
Promise().then(() => {
ReactDOM.unstable_batchedUpdates(() => {
this.setState({ k: 1 })
this.setState({ x: 2 })
})
})
复制代码
原理:
在React的setState函数中,会根据一个变量isBatchingUpdates来判断当前变更是直接更新state还是将其加入队列中,isBatchingUpdates的默认值是false,但是在React调用事件处理函数的时候,会先调用bachedUpdates,将isBatchingUpdates的值置为true,那么此时,所有的更新都会被放进队列之中,直到函数结束,此时更新state
如何获取到最新的state值?
批处理导致无法获取最新的state,如下
import React, { Component } from "react";
class App extends Component {
state = { title: 0 };
componentDidMount() {}
handleClick = () => {
// 批处理
this.setState({ title: this.state.title + 1 });
this.setState({ title: this.state.title + 1 })
console.log(this.state.title) // 0
};
render() {
const { title } = this.state;
console.log('render', this.state.title) // 'render', 1
return ( <div id="id" onClick={this.handleClick}> {title} </div> );
}
}
export default App;
复制代码
1、方案1,给setState传递一个函数,访问当前state
handleClick = () => {
// setState第一个参数为函数时,函数参数分别为prevState,prevProps,可以获取到最新state
this.setState((prevState) => ({ title: prevState.title + 1 }));
this.setState((prevState) => ({ title: prevState.title + 1 }));
}
render () {
console.log('render', this.state.title) // 2
return <div id="id" onClick={this.handleClick}>{title}</div>
}
复制代码
2、方案2,使用setState的第二个参数,回调函数, 获取最新state
// 回调函数可以获取最新的state
handleClick = () => {
this.setState({ title: this.state.title + 1 }, () => {
console.log(this.state.title) // 1
})
console.log(this.state.title) // 0
}
render() {
console.log('render', this.state.title) // 1
return ( <div id="id" onClick={this.handleClick}> {title} </div> );
}
复制代码
注:回调函数是在setState完成,并且render 执行之后调用的
handleClick = () => {
this.setState( (prevState) => ({ title: prevState.title + 1 }), () => {
console.log("1", this.state); // "1",2
} );
this.setState( (prevState) => ({ title: prevState.title + 1 }), () => {
console.log("2", this.state); // "2", 2
} );
}
render() {
console.log('render', this.state.title) // 2
return ( <div id="id" onClick={this.handleClick}> {title} </div> );
}
复制代码
以上包含一些个人理解,如果不对请指出,谢谢!
参考文档:
github.com/sisterAn/bl…
react官方文档