内容简介:本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出来我的来我的个人博客 获得无与伦比的阅读体验
本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出
来我的 GitHub repo 阅读完整的专题文章
来我的个人博客 获得无与伦比的阅读体验
React使用一个特殊的对象 this.state
来管理组件内部的状态。
然后开发者就可以通过描述状态来控制UI的表达。
如何描述状态呢?
一般我们会在 constructor
生命周期钩子初始化状态。
import React, { Component } from 'react'; class App extends Component { constructor(props) { super(props); this.state = { name: '', star: 0 }; } } export default App; 复制代码
也可以直接用属性初始化器的写法,看起来更加简洁。
然后通过 this.setSatate()
来改变状态。
import React, { Component } from 'react'; class App extends Component { state = { name: '', star: 0 }; componentDidMount() { this.setState({ name: 'react', star: 1 }); } } export default App; 复制代码
this.state
首先,改变状态有特殊的门路
开发者不能直接改变 this.state
的属性,而是要通过 this.setSatate
方法。
为什么要这样设计?
可能是为了更加语义化吧,开发者清楚自己在更新状态,而不是像Vue那样改变于无形。
不过别急,我为正在阅读的你准备了一个炸弹:
猜猜下面例子最终渲染出来的star是多少?
import React, { Component } from 'react'; class App extends Component { state = { star: 0 }; componentDidMount() { this.state.star = 1000; this.setState(prevState => ({ star: prevState.star + 1 })); } // componentDidMount() { // this.setState(prevState => ({ star: prevState.star + 1 })); // this.state.star = 1000; // } // componentDidMount() { // this.state.star = 1000; // this.setState({ star: this.state.star + 1 }); // } // componentDidMount() { // this.setState({ star: this.state.star + 1 }); // this.state.star = 1000; // } } export default App; 复制代码
答案是1001。
诶,不是说不能直接改变 this.state
的属性么?
听我讲,首先, this.state
并不是一个不可变对象,你(非得较劲的话)是可以直接改变它的属性的。但是它不会触发 render
生命周期钩子,也就不会渲染到UI上。
不过,既然你确实改变了它的值,如果之后调用了 this.setSatate()
的话,它会在你直接改变的值的基础上再做更新。
所以呀少年,要想不懵逼,得靠我们自己的代码规范。
至于注释的部分,只是为了说明顺序问题。
第一部分注释渲染出来的star是1001。因为回调会首先计算star的值,而这时候star的值是1000。
第二部分注释渲染出来的star是1001。这很好理解。
第三部分注释渲染出来的star是1。这也好理解,这个时候star的值还是0。
其次,状态更新会合并处理
大家也看到了,我们可以每次更新部分状态。
新状态并不会覆盖旧状态,而是将已有的属性进行合并操作。如果旧状态没有该属性,则新建。
这类似于 Object.assign
操作。
而且合并是浅合并。
只有第一层的属性才会合并,更深层的属性都会覆盖。
import React, { Component } from 'react'; class App extends Component { state = { userInfo: { name: '', age: 0 } }; componentDidMount() { this.setState({ userInfo: { age: 13 } }); } } export default App; 复制代码
最后,可以有不是状态的状态
如果你需要存储某种状态,但是不希望在状态更新的时候触发 render
生命周期钩子,那么完全可以直接存储到实例的属性上,只要不是 this.state
的属性。使用起来还是很自由的。
异步更新
什么叫异步更新?
异步更新说的直白点就是批量更新。
它不是真正的异步,只是React有意识的将状态攒在一起批量更新。
React组件有自己的生命周期,在某两个生命周期节点之间做的所有的状态更新,React会将它们合并,而不是立即触发UI渲染,直到某个节点才会将它们合并的值批量更新。
以下,组件更新之后 this.state.star
的值是1。
import React, { Component } from 'react'; class App extends Component { state = { star: 0 }; componentDidMount() { this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); } } export default App; 复制代码
因为这些状态改变的操作都是在组件挂载之后、组件更新之前,所以实际上它们并没有立即生效。
this.state.star
的值一直是0,尽管状态被多次操作,它得到的值一直是1,因此合并之后 this.state.star
的还是1,并不是我们直觉以为的3。
为什么要异步更新?
因为 this.setSatate()
会触发 render
生命周期钩子,也就会运行组件的diff算法。如果每次setState都要走这一套流程,不仅浪费性能,而且是完全没有必要的。
所以React选择了在一定阶段内批量更新。
还是以生命周期为界,挂载之前的所有setState批量更新,挂载之后到更新之前的所有setState批量更新,每次更新间隙的所有setState批量更新。
非异步情况
再来看一种情况:
猜猜最终渲染出来的star是多少?
import React, { Component } from 'react'; class App extends Component { state = { star: 0 }; timer = null; componentDidMount() { this.timer = setTimeout(() => { this.setState({ num: this.state.star + 1 }); this.setState({ num: this.state.star + 1 }); this.setState({ num: this.state.star + 1 }); }, 5000); } componentWillUnmount() { clearTimeout(this.timer); } } export default App; 复制代码
答案是3。
卧槽!
说实话,这里我也没想明白。
我在React仓库的Issues里提过这个情况,这是React主创之一Dan Abramov的回答:
setState is currently synchronous outside of event handlers. That will likely change in the future.
Dan Abramov所说的 event handlers
应该指的是React合成事件回调和生命周期钩子。
我的理解,因为只有这些方法才能回应事件,所以它们之中的状态更新是批量的。但是它们之中的异步代码里有状态更新操作,React就不会批量更新,而是符合直觉的样子。
我们看下面的例子,正常的重复setState只会触发一次更新,但是http请求回调中的重复setState却会多次触发更新,看来异步的setState不在React掌控之内。
import React, { Component } from 'react'; class App extends Component { state = { star: 0 }; componentDidMount() { fetch('https://api.github.com/users/veedrin/repos') .then(res => res.json()) .then(res => { console.log(res); this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); this.setState({ star: this.state.star + 1 }); }); } } export default App; 复制代码
还有一种情况就是原生的事件回调,比如document上的事件回调,也不是异步的。
总结一下:所谓的异步只是批量更新而已。真正异步回调和原生事件回调中的setState不是批量更新的。
不过,Dan Abramov早就提到过,会在将来的某个版本(可能是17大版本)管理所有的setState,不管是不是在所谓的 event handlers
之内。
React的设计有一种简洁之美,从这种对待开发者反馈的态度可见一斑。
回调
既然 this.setSatate()
的设计不符合直觉,React早就为开发者提供了解决方案。
this.setSatate()
的参数既可以是一个对象,也可以是一个回调函数。函数返回的对象就是要更新的状态。
回调函数提供了两个参数,第一个参数就是计算过的state对象,即便这时还没有渲染,得到的依然是符合直觉的计算过的值。同时,贴心的React还为开发者提供了第二个参数,虽然并没有什么卵用。
以下,组件更新之后 this.state.star
的值是3。
有一个小细节:箭头函数如果直接返回一个对象,要包裹一层小括号,以区别块级作用域。
import React, { Component } from 'react'; class App extends Component { state = { star: 0 }; componentDidMount() { this.setState((prevState, prevProps) => ({ star: prevState.star + 1 })); this.setState((prevState, prevProps) => ({ star: prevState.star + 1 })); this.setState((prevState, prevProps) => ({ star: prevState.star + 1 })); } } export default App; 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。