這幾天在閱讀徐超老師的《React 進階之路》,然後在看看自己之前的《React Native移動開發實戰》,發現之前我自己的書部分寫的比較的淺顯,最近打算對基礎部分進行更新,加大基礎部分,特别是React基礎部分的講解,并對React Native提供的元件部分進行更新。
衆所周知,React架構的核心思想是元件化,一個應用程式由多個元件搭建而成,元件最重要的概念是State(狀态),State是一個元件的UI資料模型,是元件渲染時的資料依據。
前面一篇文章我們主要介紹了
React元件 相關的内容,但是對于元件的Props和State并沒有做過多的介紹,本文就着重介紹元件的State。定義State
衆所周知,State作為元件的私有屬性,主要用于對元件的私有屬性進行管理,通過對屬性的狀态的監聽去渲染UI,進而完成使用者資料和界面展示的一緻性。
定義State是建立元件的第一步,定義的State必須能代表一個元件UI呈現的完整狀态集,即元件的任何UI改變,都可以從State的變化中反映出來;同時,State還必須是代表一個元件UI呈現的最小狀态集,即State中的所有狀态都是用于反映元件UI的變化,沒有任何多餘的狀态,也不需要通過其他狀态計算而來的中間狀态。
元件中定義的變量是不是應該作為元件State,可以通過下面的4條依據進行判斷:
- 這個變量是否是通過Props從父元件中擷取?如果是,那麼它不是一個狀态。
- 這個變量是否在元件的整個生命周期中都保持不變?如果是,那麼它不是一個狀态。
- 這個變量是否可以通過其他狀态(State)或者屬性(Props)計算得到?如果是,那麼它不是一個狀态。
- 這個變量是否在元件的render方法中使用?如果不是,那麼它不是一個狀态。
State 與 Props
除了State, 元件的Props也是群組件的UI展示有關的。他們之間的主要差別是:State是可變的,是元件内部維護的一組用于反映元件UI變化的狀态集合;而Props對于使用它的元件來說,是隻讀的,要想修改Props,隻能通過該元件的父元件修改。在元件狀态上移的場景中,父元件正是通過子元件的Props, 傳遞給子元件其所需要的狀态。
修改State的正确姿勢
1.不能直接修改State。
在React中,直接修改state并不會觸發render函數,是以下面的寫法是錯誤的。
// 錯誤this.state.title = 'React';
元件的State隻能通過setState()方式進行修改。例如:
// 正确this.setState({title: 'React'});
2. State 的更新是異步的
調用setState,元件的state并不會立即改變,setState隻是把要修改的狀态放入一個隊列中,React會優化真正的執行時機,并且React會出于性能原因,可能會将多次setState的狀态修改合并成一次狀态修改。是以不要依賴目前的State,計算下個State。當真正執行狀态修改時,依賴的this.state并不能保證是最新的State,因為React會把多次State的修改合并成一次,這時,this.state将還是這幾次State修改前的State。另外需要注意的事,同樣不能依賴目前的Props計算下個狀态,因為Props一般也是從父元件的State中擷取,依然無法确定在元件狀态更新時的值。
舉個例子,對于一個電商類應用,在我們的購物車中,當我們點選一次購買數量按鈕,購買的數量就會加1,如果我們連續點選了兩次按鈕,就會連續調用兩次
this.setState({quantity: this.state.quantity + 1})
,在React合并多次修改為一次的情況下,相當于等價執行了如下代碼:
Object.assign(
previousState,
{quantity: this.state.quantity + 1},
{quantity: this.state.quantity + 1}
)
于是乎,後面的操作覆寫掉了前面的操作,最終購買的數量隻增加了1個。如果我們要實作加2的效果,可以使用另一個接收一個函數作為參數的setState,這個函數有兩個參數,第一個是目前最新狀态(本次元件狀态修改後的狀态)的前一個狀态preState(本次元件狀态修改前的狀态),第二個參數是目前最新的屬性props。
// 正确
this.setState((preState, props) => ({
counter: preState.quantity + 1;
}))
3. State 的更新是一個淺合并的過程
當調用setState修改元件狀态時,隻需要傳入發生改變的State,而不是元件完整的State,因為元件State的更新是一個淺合并(Shallow Merge)的過程。例如,一個元件的狀态為:
this.state = {
title : 'React',
content : 'React is an wonderful JS library!'}
當隻需要修改狀态title時,隻需要将修改後的title傳給setState即可。
this.setState({title: 'Reactjs'});
React會合并新的title到原來的元件狀态中,同時保留原有的狀态content,合并後的State的内容為:
{
title : 'Reactjs',
content : 'React is an wonderful JS library!'
}
State與Immutable
React官方建議把State當作是不可變對象,一方面是如果直接修改this.state,元件并不會重新render;另一方面State中包含的所有狀态都應該是不可變對象。當State中的某個狀态發生變化,我們應該重新建立這個狀态對象,而不是直接修改原來的狀态。那麼,當狀态發生變化時,如何建立新的狀态呢?主要有以下三種情況:
1. 狀态的類型是不可變類型(數字,字元串,布爾值,null, undefined)
這種情況最簡單,因為狀态是不可變類型,直接給要修改的狀态賦一個新值即可。例如:
this.setState({
count: 1,
title: 'Redux',
success: true})
2. 狀态的類型是數組
如有一個數組類型的狀态books,當向books中增加一本書時,使用數組的concat方法或ES6的數組擴充文法(spread syntax)即可。
// 方法一:将state先指派給另外的變量,然後使用concat建立新數組
var books = this.state.books;
this.setState({
books: books.concat(['React Guide']);
})
// 方法二:使用preState、concat建立新數組
this.setState(preState => ({
books: preState.books.concat(['React Guide']);
}))
// 方法三:ES6 spread syntax
this.setState(preState => ({
books: [...preState.books, 'React Guide'];
}))
當需要從books中截取部分元素作為新狀态時,使用數組的slice方法。例如:
// 方法一:将state先指派給另外的變量,然後使用slice建立新數組
var books = this.state.books;
this.setState({
books: books.slice(1,3);})
// 方法二:使用preState、slice建立新數組
this.setState(preState => ({
books: preState.books.slice(1,3);}))
當從books中過濾部分元素後,作為新狀态時,使用數組的filter方法。
// 方法一:将state先指派給另外的變量,然後使用filter建立新數組
var books = this.state.books;
this.setState({
books: books.filter(item => {
return item != 'React';
});
})
// 方法二:使用preState、filter建立新數組
this.setState(preState => ({
books: preState.books.filter(item => {
return item != 'React';
});
}))
注意:不要使用push、pop、shift、unshift、splice等方法修改數組類型的狀态,因為這些方法都是在原數組的基礎上修改,而concat、slice、filter會傳回一個新的數組。
3. 狀态的類型是普通對象(不包含字元串、數組)
1,使用ES6 的Object.assgin方法。
// 方法一:将state先指派給另外的變量,然後使用Object.assign建立新對象
var owner = this.state.owner;this.setState({
owner: Object.assign({}, owner, {name: 'Jason'});
})
// 方法二:使用preState、Object.assign建立新對象
this.setState(preState => ({
owner: Object.assign({}, preState.owner, {name: 'Jason'});
}))
2,使用對象擴充文法
// 方法一:将state先指派給另外的變量,然後使用對象擴充文法建立新對象
var owner = this.state.owner;this.setState({
owner: {...owner, name: 'Jason'};
})
// 方法二:使用preState、對象擴充文法建立新對象
this.setState(preState => ({
owner: {...preState.owner, name: 'Jason'};
}))
總結一下,建立新的狀态對象的關鍵是,避免使用會直接修改原對象的方法,而是使用可以傳回一個新對象的方法。當然,也可以使用一些Immutable的JS庫(如
Immutable.js)來實作類似的效果。
那麼,為什麼React推薦元件的狀态是不可變對象呢?一方面是因為不可變對象友善管理和調試;另一方面是出于性能考慮,當對象元件狀态都是不可變對象時,我們在元件的shouldComponentUpdate方法中,僅需要比較狀态的引用就可以判斷狀态是否真的改變,進而避免不必要的render調用。當我們使用React 提供的PureComponent時,更是要保證元件狀态是不可變對象,否則在元件的shouldComponentUpdate方法中,狀态比較就可能出現錯誤,因為PureComponent執行的是淺比較。
原文釋出時間為:2018年06月26日
原文作者:掘金
本文來源:
掘金如需轉載請聯系原作者