設計state
state是跟UI相關的,元件的任何UI改變都可以從state的變化中反映出來。state代表一個元件UI呈現的最小狀態,沒有任何多餘的狀態,也不應該存在通過其他狀態計算而來的中間狀態。
1. 多餘的中間狀態
//totalCost 就是一個多餘的狀態,因為其可以通過list計算而來。
{
list: [], //購物車列表
totalCost: 0 //總價格
}
2. 不跟UI相關的變數作為元件例項的普通屬性
// 計時器例子
componentDidMount() {
this.timer = setInterval(this.updateDate, 1000)
}
複製程式碼
state一般又分為兩種型別:
1 用作渲染元件使用到的資料來源
2 用作元件UI展示的判斷依據
this,state = {
user: "Tom", //展示內容資料
isLogin: true // 展示判斷依據
}
{ this.state.isLogin ? <h1>Hello, {this.state.user}</h1> : null }複製程式碼
修改state
狀態可以通過this.state.xxx來直接獲取,但是修改state缺需要多多注意:
1. 不能直接修改state
直接修改state,儘管不會報錯,但是不會觸發render,正確的方法是使用setState
this.setState({ title: "react"})
2. state的更新是非同步的
呼叫setState並不會立刻改變state,而是把要修改的狀態放入一個佇列中,React會優化真正的執行時機。可能會將多次setState的狀態修改合併到一次狀態修改。所以不能依賴當前的state,計算下一個state。
比如,點選一個購買按鈕,購買的數量就會加1。如果連續點選兩次,則會連續兩次呼叫this.setState({ quantity: this.state.qunatity + 1 })。 在合併為一次修改得情況下,相當於等價執行如下程式碼:
Object.assign(previousState, {quantity: this.state.qunatity + 1 }, {quantity: this.state.qunatity + 1 })複製程式碼
最終購買的數量只增加1。
所以解決辦法是,呼叫setState,傳入一個函式作為引數。這個函式有兩個引數,第一個是當前的最新狀態的前一個狀態preState,第二個引數是當前最新的屬性props
this.setState((preState, props)=> ({
counter: preState.quantity + 1
}) )複製程式碼
3. state的更新是一個合併的過程
當呼叫setState修改元件狀態時,只需傳入發生改變的state,而不是元件完整的state,因為組建的state的更新過程是一個合併的過程。
this,state = {
user: "Tom", //展示內容資料
isLogin: true // 展示判斷依據
}
// 只需傳入改變的state
this.setState({
user: "Tony"
})複製程式碼
不可變物件
React官方建議把state當做不可變物件,一方面,直接修改this.state,元件並不會重新render;另一方面,state中包含的所有狀態都應該是不可變物件。當state中的某個狀態返生變化,應該重新建立這個狀態物件,而不是直接修改原來的狀態。如何建立,分三種情況:
1. 狀態的型別是不可變型別(數字 字串 布林值 null undefined)
因為狀態是不可變型別,所以直接給要修改得狀態賦值一個新值即可
2. 狀態的型別是陣列
不能使用push、pop、shift、unshift、splice等方法修改陣列型別的狀態,因為這些方法都是在原陣列的基礎上修改,而concat、slice、filter會返回一個新的陣列。也可以使用ES6的陣列擴充套件語法
通常如果需要使用push、splice等方法,首先會先呼叫slice方法,返回一個新的組數,在新陣列上使用push等方法。
this.setState(preState => ({
books: preState.books.concat(["nodejs"]}
}))
複製程式碼
this.setState(preState => ({
books: [...preState.books, "nodej"]
}))複製程式碼
3. 狀態的型別是普通物件
// 使用ES6的Object.assgin 方法
this.setState(preState => ({
people: Object.assign({}, preState.owner, {name: "Tom"})
}))複製程式碼
// 使用物件擴充套件方法
this.setState(preState => ({
people: {...preState.owner, name: "Tom"}
}))複製程式碼
為什麼React推薦元件的狀態是不可變物件,原因大概有兩點:
1. 防止原有物件意外修改。雖然react使用setState來改變state,但是直接this.state.xx = yy直接賦值並不會報錯,只是不會觸發render,但是state確實發生了變化,有可能導致不可預期的錯誤。
2. 處於效能考慮,可以在shouldComponentUpdate方法中僅僅比較兩次狀態物件的引用就可以判斷狀態是否發生了改變。 尤其我們在使用pureComponent元件時,其內部會在shouldComponentUpdate執行“淺比較”,就是隻比較this.state.aa === nextState.aa,通過判斷物件引用是否變化,如果物件變化但是引用沒有變,及在原有物件修改(push、pop等方法),也不會觸發render,這個要特別注意了。