[轉] 深刻理解React 組件狀態(State)

React 的核心思想是組件化的思想,應用由組件搭建而成,而組件中最重要的概念是State(狀態),State是一個組件的UI數據模型,是組件渲染時的數據依據。javascript

一. 如何定義State

定義一個合適的State,是正確建立組件的第一步。State必須能表明一個組件UI呈現的完整狀態集,即組件的任何UI改變,均可以從State的變化中反映出來;同時,State還必須是表明一個組件UI呈現的最小狀態集,即State中的全部狀態都是用於反映組件UI的變化,沒有任何多餘的狀態,也不須要經過其餘狀態計算而來的中間狀態。css

組件中用到的一個變量是否是應該做爲組件State,能夠經過下面的4條依據進行判斷:html

  1. 這個變量是不是經過Props從父組件中獲取?若是是,那麼它不是一個狀態。
  2. 這個變量是否在組件的整個生命週期中都保持不變?若是是,那麼它不是一個狀態。
  3. 這個變量是否能夠經過其餘狀態(State)或者屬性(Props)計算獲得?若是是,那麼它不是一個狀態。
  4. 這個變量是否在組件的render方法中使用?若是不是,那麼它不是一個狀態。這種狀況下,這個變量更適合定義爲組件的一個普通屬性,例如組件中用到的定時器,就應該直接定義爲this.timer,而不是this.state.timer。

請務必牢記,並非組件中用到的全部變量都是組件的狀態!當存在多個組件共同依賴一個狀態時,通常的作法是狀態上移,將這個狀態放到這幾個組件的公共父組件中。java

二. State 與 Props 區別

除了State, 組件的Props也是和組件的UI有關的。他們之間的主要區別是:State是可變的,是組件內部維護的一組用於反映組件UI變化的狀態集合;而Props對於使用它的組件來講,是隻讀的,要想修改Props,只能經過該組件的父組件修改。在組件狀態上移的場景中,父組件正是經過子組件的Props, 傳遞給子組件其所須要的狀態。git

三. 如何正確修改State

1.不能直接修改State。

直接修改state,組件並不會從新重發render。例如:github

// 錯誤 this.state.title = 'React'; 

正確的修改方式是使用setState():redux

// 正確 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合併屢次修改成一次的狀況下,至關於等價執行了以下代碼:ecmascript

Object.assign( previousState, {quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1} ) 

因而乎,後面的操做覆蓋掉了前面的操做,最終購買的數量只增長了1個。異步

若是你真的有這樣的需求,可使用另外一個接收一個函數做爲參數的setState,這個函數有兩個參數,第一個是當前最新狀態(本次組件狀態修改後的狀態)的前一個狀態preState(本次組件狀態修改前的狀態),第二個參數是當前最新的屬性props。以下所示:

// 正確 this.setState((preState, props) => ({ counter: preState.quantity + 1; })) 

3. State 的更新是一個淺合併(Shallow Merge)的過程。

當調用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)

這種狀況最簡單,由於狀態是不可變類型,直接給要修改的狀態賦一個新值便可。如要修改count(數字類型)、title(字符串類型)、success(布爾類型)三個狀態:

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. 狀態的類型是普通對象(不包含字符串、數組)

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'}); })) 

3.2 使用對象擴展語法(object spread properties

// 方法一:將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執行的是淺比較(比較對象的引用)。

做者:蒼山沭河連接:https://www.jianshu.com/p/c6257cbef1b1來源:簡書著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索