只要使用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必定要用不可變值?說的很詳細~
感謝閱讀~