只要使用React的小伙伴,必定接触到setState这个API,地位不言而喻react
但要使用好必定要抓住三个关键词:合并更新、同步异步、不可变值数组
咋一看,有些小伙伴可能云里雾里~性能优化
别急,我挨个解释markdown
先看一个例子🌰网络
import { Component } from 'react'
class App extends Component {
state= {
count: 1
}
handleAdd = () => {
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
{ this.state.count }
<button onClick={ this.handleAdd }>add</button>
</div>
)
}
}
复制代码
这段代码,咱们重复的setState让count不断加1异步
可咱们会发现点击一次按钮仅加1函数
而不是咱们想一想加4post
其实就是React的底层优化性能
他会把多个setState中的更新的对象合并一次更新优化
底层相似使用 Object.assign
对更新对象进行合并处理
// 等价于 { count: this.state.count + 1 }
Object.assign({
count: this.state.count + 1
}, {
count: this.state.count + 1
}, {
count: this.state.count + 1
}, {
count: this.state.count + 1
})
复制代码
那如何避免不会被合并呢?
可使用函数,函数不会被合并
// preState 更新以前的 state
// preProps 更新以前的 props
this.setState((preState, preProps) => {
// return 此次要更新的对象
})
复制代码
来一个例子🌰
import { Component } from 'react'
class App extends Component {
state= {
count: 1
}
handleAdd = () => {
this.setState((preState) => {
console.log(preState) // {count: 1}
return ({
count: preState.count + 1
})
})
this.setState((preState) => {
console.log(preState) // {count: 2}
return ({
count: preState.count + 1
})
})
this.setState((preState) => {
console.log(preState) // {count: 3}
return ({
count: preState.count + 1
})
})
this.setState((preState) => {
console.log(preState) // {count: 4}
return ({
count: preState.count + 1
})
})
}
render() {
return (
<div>
{ this.state.count }
<button onClick={ this.handleAdd }>add</button>
</div>
)
}
}
复制代码
这是什么意思呢?
直接来个例子🌰
import { Component } from 'react'
class App extends Component {
state= {
count: 1
}
handleAdd = () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count)
}
render() {
return (
<div>
{ this.state.count }
<button onClick={ this.handleAdd }>add</button>
</div>
)
}
}
复制代码
效果图:
看着效果图,小伙伴其实能够一目了然发现
在 console.log
以前其实已经 setState
但获取的值却仍是从 1 开始打印,而不是从 2 开始
也就是说 console.log
以前还未 setState
那此时 setState
即是异步
更新
但咱们可使用 setState(updater, callback)
第二个参数(回调函数)
相似 Vue 中的 $nextTick
// 代码同上
// ...
handleAdd = () => {
this.setState({
count: this.state.count + 1
}, () => {
console.log(this.state.count) // 此时 count 等于2
})
}
// ...
复制代码
固然你也能够在生命周期 componentDidUpdate
拿到所有完成更新的值
componentDidUpdate() {
console.log(this.state.count) // 此时 count 等于2
}
复制代码
那什么时候同步?
异步函数能够是setTimeout
、setInterval
、requestAnimationFrame
class App extends Component {
state= {
count: 1
}
handleAdd = () => {
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 此时 count 等于2
}, 0)
}
render() {
return (
<div>
{ this.state.count }
<button onClick={this.handleAdd}>add</button>
</div>
)
}
}
复制代码
效果图:
从代码和上面的图,咱们很容易看出
咱们仍是在 console.log
以前 setState
可是打印是从 2 开始的
那就能够说明此时setState
是同步
更新的
相似自定义事件,附上例子🌰
import { Component } from 'react'
class App extends Component {
state= {
count: 1
}
componentDidMount() {
// 自定义事件
this.handleAdd = event => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 此时 count 等于2
}
const button = document.getElementById('button')
button.addEventListener('click', this.handleAdd);
}
componentWillUnmount() {
// 移除自定义事件,防止内存泄漏
const button = document.getElementById('button')
button.removeEventListener('click', this.handleAdd);
}
render() {
return (
<div>
{ this.state.count }
<button id='button'>add</button>
</div>
)
}
}
复制代码
一张很经典的图
(图源于网络)
这图什么意思呢?
其实说白就是 React 用 isBatchingUpdates 去判断 setState 后该如何处理
咱们把点击事件拿出举例🌰
React 会在 setState 以前,isBatchingUpdates = true
到结束时才会让,isBatchingUpdates = false
此时,命中图中左侧判断:保存组件于 dirtyComponents 中
handleAdd = () => {
// 开始时处于BathingUpdates, isBatchingUpdates = true
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // 此时 count 等于 1
// 结束时 isBatchingUpdates = false
}
复制代码
而同步时呢?
一样,setState 以前,isBatchingUpdates = true
结束时,isBatchingUpdates = false
此时,isBatchingUpdates = false 才 setState 开始工做
命中图中的右侧的判断,立刻开始一系列的更新数值
handleAdd = () => {
// ①开始时处于BathingUpdates, isBatchingUpdates = true
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count) // ③此时 count 等于2
}, 0)
// ②结束时 isBatchingUpdates = false
}
复制代码
使用不可变值,其实就是为了页面的性能优化
咱们知道其实有些页面组件没有更新的必要
例如:父组件更新,子组件也会随着更新
但子组件其实没有数值的更新,因此不必更新渲染
咱们可使用 shouldComponentUpdate
手动控制更新渲染
shouldComponentUpdate(nextProps, nextState) {
//...
// 若是 return false 表明组件不更新渲染
// 若是 return true 表明组件更新渲染
}
复制代码
因此,咱们就要用不可变值,两个先后不同的props或是state对象进行比较
例如
// ...
// 传入数组list
shouldComponentUpdate(nextProps, nextState) {
// 引入 lodash 的 isEqual 比较数组值是否相等
if (isEqual(nextProps.list,this.props.list)) {
return false // 组件不更新
}
return false // 组件不更新
}
复制代码
若是你不使用不可变值,那指向有多是同一个对象
那么即时你使用shouldComponentUpdate
,也会更新渲染
由于他们都是同一个对象
若是小伙伴不太明白~
那就看看我以前写的文章 如何理解react中的setState必定要用不可变值?说的很详细~
感谢阅读~