"小和山的菜鳥們",爲前端開發者提供技術相關資訊以及系列基礎文章。爲更好的用戶體驗,請您移至咱們官網小和山的菜鳥們 ( xhs-rookies.com/ ) 進行學習,及時獲取最新文章。前端
"Code tailor" ,若是您對咱們文章感興趣、或是想提一些建議,微信關注 「小和山的菜鳥們」 公衆號,與咱們取的聯繫,您也能夠在微信上觀看咱們的文章。每個建議或是贊同都是對咱們極大的鼓勵!react
這節咱們將介紹 React
中 setState
,但願能夠幫助你們真正理解 setState
。git
本文會向你介紹如下內容:github
setState
State
setState()
setState
多是異步更新setState
的合併在介紹 setState
以前,咱們先來看一個 setState
的案例,瞭解一下是如何使用的。web
咱們來展現一個使用案例,當點擊一個 改變文本
的按鈕時,修改界面前顯示的內容:服務器
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
函數,頁面就會被從新渲染。微信
點擊按鈕以後,從新渲染的效果:markdown
將上面的 changeMessage
方法,改爲下面的樣子。dom
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(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
已經修改以後的結果。也就是從新渲染以後執行的內容。
咱們來看下面的代碼:
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
上也有不少的討論;咱們來對他的回答作一個簡單的總結:
setState
設計爲異步,能夠顯著的提高性能;
若是每次調用 setState
都進行一次更新,那麼意味着 render
函數會被頻繁調用,界面從新渲染,這樣效率是很低的;
注意: 最好的辦法應該是獲取到多個更新,以後進行批量更新;
state
,可是尚未執行 render
函數,那麼 state
和 props
不能保持同步;注意:
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); // 你好啊,小和山
})
}
複製代碼
其實分紅兩種狀況:
React
合成事件中,setState
是異步;setTimeout
或者原生 dom
事件中,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."
});
}
複製代碼
好比咱們仍是有一個 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
中受控組件和非受控組件的內容,敬請期待!