本文会向你介绍以下内容:
在介绍 setState 之前,我们先来看一个 setState 的案例,了解一下是如何使用的。
我们来展示一个使用案例,当点击一个 改变文本 的按钮时,修改界面前显示的内容:
![](https://img-blog.csdnimg.cn/img_convert/15bb6dff9a2bfc1f1aac45ea350eabbd.webp?x-oss-process=image/format,png#clientId=ubecc856d-2fbf-4&from=paste&id=ud3a5056c&margin=[object Object]&originHeight=140&originWidth=140&originalType=url&ratio=1&status=done&style=none&taskId=u43336020-69bc-45f9-9e7c-41339f8aede)
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
message: 'Hello React',
}
}
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={(e) => this.changeMessage()}>ChangeMessage</button>
</div>
)
}
changeMessage() {
this.setState({
message: 'Hello xhs,your message is changed.',
})
}
}
state 的初始化是在类构造器中去设置的,然后如果想要更改 state ,那就是通过 setState 函数,该函数最主要的就是传入一个对象作为参数,而该对象就是你想要修改的值。上述代码中,当你点击 ChangeMessage 时,就会调用 setState 函数,而 setState 会调用 render 函数,页面就会被重新渲染。
点击按钮之后,重新渲染的效果:
将上面的 changeMessage 方法,改成下面的样子。
changeMessage() {
this.state.message = "Hello xhs,your message is changed.";
}
点击 ChangeMessage 之后,页面并没有改变,但是打印 state 你会发现,state 中的 message 变了,但是页面不会被重新渲染。
构造函数,是我们唯一可以给 this.state 赋值的地方,而为了可以重新渲染页面,那就只能通过 setState 函数来修改。
所以,我们通过调用 setState 来修改数据:
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
})
}
setState(updater, [callback])
setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式
将 setState() 视为 请求 而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。
setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发。
1. 接受对象类型
setState(stateChange[, callback])
stateChange 会将传入的对象浅层合并到新的 state 中,例如 让 count +1
this.setState({ count: this.state.count + 1 })
这种形式的 setState() 也是异步的,并且在同一周期内会对多个 setState 进行批处理。例如,如果在同一周期内多次 count + 1,则相当于:
Object.assign(
previousState, // 之前的状态
{count: state.count + 1},
{count: state.count + 1},
...
)
后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。如果后续状态取决于当前状态,我们建议使用 updater 函数 的形式代替:
this.setState((state) => {
return { count: state.count + 1 }
})
2. 接受函数参数
我们在初始的 state 中并没有一个 count 。但是现在我们有一个需求:那就是添加一个 count 在 state 中,使用对象作为第一参数,你就会发现这样一个问题。
changeCount () {
this.setState({
count: 0
}) // => this.state.count 是 undefined
this.setState({
count: this.state.count + 1
}) // => undefined + 1 = NaN
this.setState({
count: this.state.count + 2
}) // => NaN + 2 = NaN
}
上面的代码的运行结果并不能达到我们的预期,我们希望 count 运行结果是 3 ,可是最后得到的是 NaN。但是这种后续操作依赖前一个 setState 的结果的情况并不罕见。
这里就自然地引出了 setState 的第二种使用方式,可以接受一个函数作为参数。React.js 会把上一个 setState 的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state 的对象:
changeCount () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 }
// 上一个 setState 的返回是 count 为 0,这里需要执行 +1 所以当前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 }
// 上一个 setState 的返回是 count 为 1,这里需要执行 +2 所以当前返回 3
})
// 最后的结果是 this.state.count 为 3
}
这样就可以达到上述的_利用上一次 setState 结果进行运算_的效果。
setState() 的第二个参数为可选的回掉函数,它将在 setState 完成合并并重新渲染组件后执行。通常,我们建议使用 componentDidUpdate() 来代替此方式。
我们来给案例中的 changeMessage 函数添加两个打印方式。
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
},() => {
console.log('callback result: ',this.state.message);
})
console.log('no callback result:',this.state.message);
}
从图片中我们可以看出
正因为不是立马拿到我们想要的结果,所以这就是我们接下来要讲的 setState 可能是异步更新。
这样我们就可以知道 setState 的第二参数的作用,就是可以确保得到 state 已经修改之后的结果。也就是重新渲染之后执行的内容。
我们来看下面的代码:
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
})
console.log(this.state.message); // Hello React
}
为什么 setState 设计为异步呢?
我们来对他的回答做一个简单的总结:
注意: 最好的办法应该是获取到多个更新,之后进行批量更新;
注意: state 和 props 不能保持一致性,会在开发中产生很多的问题;
获取到更新后的值
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
}, () => {
console.log(this.state.message); // Hello xhs,your message is changed.
});
}
当然,我们也可以在生命周期函数:
componentDidUpdate(prevProps, provState, snapshot) {
console.log(this.state.message);
}
setState 一定是异步?
疑惑:setState一定是异步更新的吗?
验证一:在 setTimeout 中的更新:
changeText() {
setTimeout(() => {
this.setState({
message: "你好啊,小和山"
});
console.log(this.state.message); // 你好啊,小和山
}, 0);
}
验证二:原生 DOM 事件:
componentDidMount() {
const btnEl = document.getElementById("btn");
btnEl.addEventListener('click', () => {
this.setState({
message: "你好啊,小和山"
});
console.log(this.state.message); // 你好啊,小和山
})
}
其实分成两种情况:
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state
给 state 定义一些数据
this.state = {
name: 'xhs',
message: 'Hello React',
count: 0,
}
通过 setState 去修改 state 中的 message ,是不会对 name 产生影响的
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
});
}
比如我们还是有一个 count 属性,默认为 0,记录当前的数字:
increment() {
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
}
如何可以做到,让 count 最终变成 3 呢?
increment() {
this.setState((state, props) => {
return {
count: state.count + 1
}
})
this.setState((state, props) => {
return {
count: state.count + 1
}
})
this.setState((state, props) => {
return {
count: state.count + 1
}
})
}
上面的代码就是用到了,setState 接收一个函数作为第一参数的情况,来解决这个问题,就可以很好的得到我们期待的结果。
对应的回复: https://github.com/facebook/react/issues/11527#issuecomment-360199710