快来加入我们吧!
"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。
"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!
前言
这节我们将介绍 React
中 setState
,希望可以帮助大家真正理解 setState
。
本文会向你介绍以下内容:
- 如何使用
setState
- 不能直接修改
State
setState()
setState
可能是异步更新setState
的合并
如何使用 setState
在介绍 setState
之前,我们先来看一个 setState
的案例,了解一下是如何使用的。
我们来展示一个使用案例,当点击一个 改变文本
的按钮时,修改界面前显示的内容:
案例代码
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
message: 'Hello React',
}
}
render() {
return (
{this.state.message}
)
}
changeMessage() {
this.setState({
message: 'Hello xhs,your message is changed.',
})
}
}
state
的初始化是在类构造器中去设置的,然后如果想要更改 state
,那就是通过 setState
函数,该函数最主要的就是传入一个对象作为参数,而该对象就是你想要修改的值。上述代码中,当你点击 ChangeMessage
时,就会调用 setState
函数,而 setState
会调用 render
函数,页面就会被重新渲染。
点击按钮之后,重新渲染的效果:
不能直接修改 State
将上面的 changeMessage
方法,改成下面的样子。
changeMessage() {
this.state.message = "Hello xhs,your message is changed.";
}
点击 ChangeMessage
之后,页面并没有改变,但是打印 state
你会发现,state
中的 message
变了,但是页面不会被重新渲染。
构造函数,是我们唯一可以给 this.state
赋值的地方,而为了可以重新渲染页面,那就只能通过 setState
函数来修改。
所以,我们通过调用 setState
来修改数据:
- 当我们调用
setState
时,会重新执行render
函数,根据最新的State
来创建ReactElement
对象 - 再根据最新的
ReactElement
对象,对DOM
进行修改;
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
})
}
setState()
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
外部的时候,并不可以立马拿到我们想要的结果 callback
中则返回的是修改之后的结果。
正因为不是立马拿到我们想要的结果,所以这就是我们接下来要讲的 setState 可能是异步更新。
这样我们就可以知道 setState
的第二参数的作用,就是可以确保得到 state
已经修改之后的结果。也就是重新渲染之后执行的内容。
setState 可能是异步更新
我们来看下面的代码:
- 最终打印结果是
Hello React
; - 可见
setState
是异步的操作,我们并不能在执行完setState
之后立马拿到最新的state
的结果
changeMessage() {
this.setState({
message: "Hello xhs,your message is changed."
})
console.log(this.state.message); // Hello React
}
为什么 setState
设计为异步呢?
setState
设计为异步其实之前在GitHub
上也有很多的讨论;- React 核心成员(Redux 的作者)Dan Abramov 也有对应的回复,可以参考一下;
我们来对他的回答做一个简单的总结:
setState
设计为异步,可以显著的提升性能;- 如果每次调用
setState
都进行一次更新,那么意味着render
函数会被频繁调用,界面重新渲染,这样效率是很低的;
注意: 最好的办法应该是获取到多个更新,之后进行批量更新;
- 如果同步更新了
state
,但是还没有执行render
函数,那么state
和props
不能保持同步;
注意:state
和props
不能保持一致性,会在开发中产生很多的问题;
获取到更新后的值
- setState 的第二参数,一个回调函数,这个回调函数会在更新后会执行;
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); // 你好啊,小和山
})
}
其实分成两种情况:
- 在组件生命周期或
React
合成事件中,setState
是异步; - 在
setTimeout
或者原生dom
事件中,setState
是同步;
setState 的合并
数据的合并
当你调用 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."
});
}
多个 setState 会被合并
比如我们还是有一个 count
属性,默认为 0
,记录当前的数字:
- 执行下面的操作之后,最后的答案是
1
- 多个
state
进行了合并
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
接收一个函数作为第一参数的情况,来解决这个问题,就可以很好的得到我们期待的结果。
下节预告
本节我们学习了 React
中 SetState
其中的奥秘,在下一个章节我们将继续学习 React
中受控组件和非受控组件的内容,敬请期待!