瞭解React中的setState
reacr元件通常都包含狀態。狀態可以是任何東西,想象一下使用場景:一個使用者是否進行了登陸,根據賬戶的啟用狀態來展示準確的使用者名稱。或者 是一系列部落格文章。或者一個模態框是否開啟以及其中的哪個標籤是啟用狀態的。包含狀態的React元件其渲染也依賴於這些狀態。當元件的狀態發生改變,元件也會 發生改變。
setState的工作機制
setState()
是在初始化狀態之後唯一合法的更新狀態的方式。比如我們有一個搜尋元件,並且想展示使用者提交的搜尋內容。
如此設定:
import React, { Component } from 'react'
class Search extends Component {
constructor(props) {
super(props)
state = {
searchTerm: ''
}
}
}
複製程式碼
我們用一個空字串來初始化狀態,並且需要通過呼叫setState()
來更新狀態searchTerm
。
setState({ searchTerm: event.target.value })
複製程式碼
此處,我們向setState()
傳遞了一個物件。這個物件包含了我們想要更新的這一部分狀態,此處,也就是searchItem
的值。React獲取這個值,然後將它合併到需要它的物件中。這有點像Search
元件在向searchTerm
狀態索取它所需要的值,setState()
給了它這個答案。
這開啟了一個React中叫做reconciliation
(調諧)的過程。調諧過程是React更新DOM的方式,根據狀態的改變來改變元件。當setState()
觸發時,React建立了一個包含元件中可響應元素(根據更新後的狀態)的新樹。通過這課樹與前樹的比較來確定Search
元件的UI如何根據狀態的改變而響應式的發生變化。React知道要實現哪些更改,並且只會在必要時更新DOM的某些部分。這就是React很快的原因。
上面聽起來有些多,所以總結成一些這些:
- 我們有一個展示搜尋項的搜尋元件。
- 搜尋專案前為控。
- 使用者提交了搜尋內容。
- 搜尋值被捕獲然後被
setState()
儲存到值中。 - 調諧進行然後React察覺到了值的變化。
- React指示搜尋元件更新值然後將搜尋項進行合併。
調諧過程沒必要改變整棵樹,除非一種情況發生,像下面這種樹的根節點發生了變化。
// old
<div>
<Search />
</div>
// new
<span>
<Search />
</span>
複製程式碼
所有的<div>
標籤變成了<span>
標籤導致整棵元件樹被重新整理。
一條經驗法則:永遠不要直接改變state。使用setState()
來改變狀態。像下面片段這樣直接修改state是無法讓元件重新渲染的。
// do not do this
this.state = {
searchTerm: event.target.value
}
複製程式碼
向'setState()'傳遞一個函式
為了進一步的演示這個想法,讓我們建立一個簡單的計數器,通過點選來增加減少數字。
讓我們來註冊元件然後為UI定義標記。
class App extends React.Component {
state = { count: 0 }
handleIncrement = () => {
this.setState({ count: this.state.count + 1 })
}
handleDecrement = () => {
this.setState({ count: this.state.count - 1 })
}
render() {
return (
<div>
<div>
{this.state.count}
</div>
<button onClick={this.handleIncrement}>Increment by 1</button>
<button onClick={this.handleDecrement}>Decrement by 1</button>
</div>
)
}
}
複製程式碼
這裡,計數器只是簡單的在每次點選後增加或者減少1。
假如如果我們想要每次增減3呢?我們或許會像下面這樣嘗試在handleIncrement
和handleDecrement
呼叫三次setState()
:
handleIncrement = () => {
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 1 })
}
handleDecrement = () => {
this.setState({ count: this.state.count - 1 })
this.setState({ count: this.state.count - 1 })
this.setState({ count: this.state.count - 1 })
}
複製程式碼
如果你自己在家裡程式設計,你可能驚訝的發現它並沒有效果。
上面的程式碼片段相當於:
Object.assign(
{},
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
{ count: this.state.count + 1 },
)
複製程式碼
Object.assign()
用於將資料從源物件拷貝到目標物件。如果拷貝的源物件有相同的鍵,像上例這樣,後面的值會覆蓋前面的值。這裡有一個簡單的Object.assign()
案例:
let count = 3
const object = Object.assign({},
{count: count + 1},
{count: count + 2},
{count: count + 3}
);
console.log(object);
// output: Object { count: 6 }
複製程式碼
所以呼叫不是執行了三次,而是一次。我們可以通過傳遞一個函式給setState()
來修復這個問題。正如你傳遞給setState()
一個物件,同樣的也可以傳遞函式,這是解決上述情況的辦法。
我們可以這樣寫handleIncrement
函式:
handleIncrement = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))
this.setState((prevState) => ({ count: prevState.count + 1 }))
}
複製程式碼
現在我們就可以通過一次點選來增加數量三次了。
在這個案例中,React不會對其進行合併,而是根據函式的順序執行,然後在函式全部執行完之後更新狀態。這會將計數更新到3而不是1.
使用更新器訪問之前的狀態
當我們構建React應用時,經常會遇到需要通過之前的狀態來己算現在的狀態。我們並不能夠在總是呼叫setState()
後,通過this.state
拿到準確的狀態值,結果是它(this.state)總是和螢幕上渲染的狀態相同。
讓我們回到計數的例子中來看看它是如何工作的。假設我們有一個每次將計數遞減1的函式。函式像下面這樣:
changeCount = () => {
this.setState({ count: this.state.count - 1})
}
複製程式碼
我們想要的是能夠減去3,changeCount()
函式被繫結在點選事件上的呼叫了3次,像這樣:
handleDecrement = () => {
this.changeCount()
this.changeCount()
this.changeCount()
}
複製程式碼
每次減少的按鈕被點選,計數減少了1而不是3。這是因為this.state.count
直到元件被重新渲染之後才進行了更新。解決方式是使用更新器。更新器允許你訪問當前狀態並立即使用它來更新其他專案。所以我們的changeCount()
函式應該是這個樣子:
changeCount = () => {
this.setState((prevState) => {
return { count: prevState.count - 1}
})
}
複製程式碼
現在我們不再依賴於this.state
的結果了。count
的狀態彼此依賴,所以我們現在可以在每次呼叫changeCount()
之後獲取到準確的狀態值。
setState()
被視為非同步————也就是說,不要總是期待呼叫setState()
之後,狀態就會被改變。
總結
在使用setState()
時,你需要知道非常重要的幾點:
- 一定使用
setState()
來更新元件狀態 setState()
可以接收物件或者函式- 當多次更新狀態時,傳遞一個函式
- 不要想在呼叫
setState()
後立即能夠使用this.state獲取準確的狀態,代替的使用更新器來完成。
原文連線
https://css-tricks.com/understanding-react-setstate/?utm_source=ponyfoo+weekly&utm_medium=email&utm_campaign=113