为什么要使用setState
开发中我们并不能直接通过修改state的值来让界面发生更新。因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是这种方式的修改React并不知道数据发生了变化。React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化,我们必须通过setState来告知React数据已经发生了变化。
如图,如果我们点击了+1,并修改了this.state的值,这个值是修改了,但是React并不知道(界面毫无反应)。Vue(Object.defineProperty,Proxy)修改了是会自动响应的,但是React和小程序都是不可以的。React必须通过setState。
我们必须告诉React修改了值,并且让它渲染最新的界面。(通过setState)
这样我们不经疑惑了,这里setState我们能直接用吗,这里(类中)也没定义啊。
我们这里App继承了Component,Component里面实现了这个方法。我们打开源码看看。(在react/index中找到component直接点进去)
setState的异步更新
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello World"
}
}
render() {
return (
<div>
<h2>当前文本: {this.state.message}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
changeText() {
this.setState({
message:"你好啊张三"
})
console.log(this.state.message)
}
}
我们看下打印的结果,我们会神奇的发现(这里文本已经变化了,打印的值还是HelloWorld)
为什么呢?因为这个setState的值是异步的更新,(不等待setState执行完,直接执行console.log)我们并不能在执行完setstate后立马拿到最新的state的结果。
为什么setState设计为异步呢?之前在Gihub上有过许多讨论。React核心成员(Redux)作者做出了解释。github.com/facebook/re… 。
中文翻译后得到的结论,我们做个总结。
- setState设计为异步,可以显著的提升性能
- 在我们开发中可能会调用很多的setState,如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
- 最好的办法应该是获取到多个更新(放入到队列中),之后进行批量更新
- 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步
- state和props不能保持一致性,会在开发中产生很多的问题
另外我们这么也是框架的一个思想,如果我们同步更新这个东西,意味着我们一旦setState,这个state的东西就立马发生改变了,而render会慢一点,因为我们要重新返回一个reactElement这个对象,还要进行diff算法,之后再更新到真实dom里,这个render会慢一步的。 假如说我们这里有个子组件。
关于第二点。比如我们有个Home的子组件。
这里props保存的还是HelloWorld
因为这是同步更新,render函数还没有执行完。这里Home(父组件里的),这个this.state.message还是原来的。而子组件里,自己的值没经过render函数。这个在更新的一刻,state和props会不一样。 这在开发中很不好。
如果我们想拿到更新之后的结果。
方式一:我们需要知道setState有两个参数(更新的state,回调函数)。这个回调函数会等到我们更新结束后,再回调它。
方式二:在compoonentDidUpdate中获取 (这个还要快于方式一,这个需要看源代码,不过知道结论就行了)
代码:
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello World"
}
}
render() {
return (
<div>
<h2>当前文本: {this.state.message}</h2>
<button onClick={e => this.changeText()}>改变文本</button>
</div>
)
}
componentDidUpdate() {
// 方式二: 获取异步更新的state
console.log(this.state.message);
}
changeText() {
this.setState({
message:"你好啊张三"
},() => {
console.log(this.state.message);
})
}
}
有一些方式让setState同步更新
我们讲两种情况。
情况一:在定时器中
情况二:
异步与同步的情况总结:
- 在组件生命周期或React合成事件(比如onClick这种)中,setState是异步
- 在setTimeout或者原生dom事件中,setState是同步
React使用合成对象可以很方便的跑在浏览器(dom),移动端(控件)。
源码中查看它异步的操作。
这个updater要在下图找,我们可以看到这就是updater调用的equeue。。。方法
点进去
上下文。其实,合成对象/生命周期与定时器,它们不是一个上下文。react会根据上下文和事件的情况返回一个sync(同步处理),还有就是Batched(批量处理)
setState数据的合并
我们思考一个问题。
其实源码里是这样的。这里会帮我们合并的。
我们打开源码来看。
setState本身的合并
我们思考一个问题。
结果是1。
这个东西是会合并的。源码中有个do while循环,对多个state进行合并。这个源码可以自己去看。
其实我们是可以阻止它合并的,让它们进行累加。这也得看源码。
increament() {
this.setState((prevState,props) => {
return {
counter: prevState.counter + 1
}
})
this.setState((prevState,props) => {
return {
counter: prevState.counter + 1
}
})
this.setState((prevState,props) => {
return {
counter: prevState.counter + 1
}
})
}
setState的数据需保持不可变
如果我们不写shouldComponentUpdate(SCU)优化,我们怎么写都行,但是写了SCU代码,就得保持setState不变。不过SCU最好是要写的。继承PureComponent跟实现了SCU的情况是一样的。
官方文档也有提到过
我们要演示这样一个案例
我们在开发中最好不要这么做,在shouldComponentUpdate(SCU)中会对state前后进行判断。这样子直接破坏了原来的数据,没有对比了。
如果我们手动加了这个函数,对比的前后就是同一个数组了。根本不可能加进去。(如果想知道为什么就要了解指针这个概念了)
在开发中我们要这么做,如图
如果我们要年龄加一,要这么做