React組件的State

組件 state必須能表明一個組件UI呈現的完整狀態集,即組件的任何UI改變均可以從 state的變化中反映出來;同時, state還必須表明一個組件UI呈現的最小狀態集,即 state中的全部狀態都用於反映組件UI的變化,沒有任何多餘的狀態,也不該該存在經過其餘狀態計算而來的中間狀態。

state vs 普通屬性

首先,什麼是普通屬性?數組

咱們的組件都是使用ES6class定義的,因此組件的屬性其實也就是class的屬性(更確切的說法是class實例化對象的屬性,但由於JavaScript本質上是沒有類的定義的,class只不過是ES6提供的語法糖,因此這裏模糊化類和對象的區別)。異步

ES6中,可使用this.{屬性名}定義一個class的屬性,也能夠說屬性是直接掛載到this下的變量。所以,stateprops實際上也是組件的屬性,只不過它們是React爲咱們在Component class中預約義好的屬性。除了stateprops之外的其餘組件屬性稱爲組件的普通屬性。函數

好比,組件中須要一個定時器來自動更新顯示時間時,咱們都會須要一個timerId來保存該定時器,已在須要的可以清除它。這就是一個普通的屬性,由於它和組件要渲染的內容沒有直接關係。性能

所以,當咱們在組件中須要用到一個變量,而且它與組件的渲染無關時,就應該把這個變量定義爲組件的普通屬性,直接掛載到this下,而不是做爲組件的state。或者更直觀的判斷是,看組件render方法中是否使用到了這個變量,若是沒有,它就是一個普通屬性優化

state vs props

stateprops又有什麼區別呢?this

stateprops都直接和組件的UI渲染有關,它們的變化都會觸發組件從新渲染,但props對於使用它的組件來講是隻讀的,是經過父組件傳遞過來的,要想修改props,只能在父組件中修改;而state是組件內部本身維護的狀態,是可變的。調試

其實區分stateprops的關鍵就是,‘控制權’是在組件自身,仍是由其父組件來控制的。code

state的判斷依據

(1)這個變量是否經過props從父組件中獲取?若是是,那麼它不是一個狀態。對象

(2)這個變量是否在組件的整個生命週期中都保持不變?若是是,那麼它不是一個狀態。生命週期

(3)這個變量是否能夠經過其餘狀態(state)或者屬性(props)計算獲得?若是是,那麼它不是一個狀態。

(4)這個變量是否在組件的render方法中使用?若是不是,那麼它不是一個狀態。這種狀況下,這個變量更適合定義爲組件的一個普通屬性。

state的修改

一、state不能直接進行修改

直接賦值形式修改state,不會觸發組件的render。修改state須要經過setState方法進行修改。

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

// 正確
this.setState({
  title: 'React'
})

二、state的更新是異步的

調用setState時,組件的state並不會當即改變,setState只是把要修改的狀態放入一個隊列中,React會優化真正的執行時機,而且出於性能緣由,可能會將屢次setState的狀態修改合併成一次狀態修改。

因此不要依賴當前的state,計算下一個state。當真正執行狀態修改時,依賴的this.state並不能保證是最新的state,由於React會把屢次state的修改合併成一次,這時this.state仍是這幾回state修改前的state

另外,須要注意的是,一樣不能依賴當前的props計算下一個狀態,由於props的更新也是異步的。

如,電商類購物車添加購買數量時,若是連續點擊兩次,就會連續調用兩次this.setState({count: this.state.count + 1}),在React合併屢次修改成一次的狀況下,至關於等價執行了下面的操做:

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

因而,最終只會增長一次操做的值。

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

this.setState((preState, props) => ({
  count: preState.count + 1;
}))

三、state更新是一個合併的過程

咱們在每次調用setState修改state時,並非須要把全部的值都進行修改,而只是設置咱們要修改的部分值。React會合並新值導員的組件state中,同時保留沒有發生變化的原來的state的值。

state與不可變對象

React官方建議把state看成不可變對象,一方面,直接修改this.state,組件並不會從新render;另外一方面,state中包含的全部狀態都應該是不可變對象。當state中的某個狀態發生變化時,應該從新建立這個狀態對象,而不是直接修改原來的狀態。

能夠分爲下面三種狀況:

1.狀態的類型是不可變類型(數字、字符串、布爾值、null、undefined)

this.setState({
  count: 1, // 數字類型
  title: 'React', // 字符串類型
  success: true // 布爾類型
})

2.狀態的類型是數組

// 方法一:使用preState、concat建立新數組
this.setState(preState => ({
  books: preState.books.concat(['React']);
}))

// 方法二:ES6 spread syntax
this.setState(preState => ({
  books: [...preState.books, 'React'];
}))

當從數組中截取部分元素做爲新的狀態時,可以使用數組的slice方法:

this.setState(preState => ({
  books: preState.books.slice(1,3);
}))

當從數組中過濾部分元素做爲新的狀態時,可以使用數組的filter方法:

this.setState(preState => ({
  books: preState.books.filter(item => {
    return item !== 'React';
  });
}))
注意,不要使用 push、pop、shift、unshift、splice等方法修改數組類型的狀態,由於這些方法都是在原數組的基礎上修改的,而 concat、slice、filter會返回一個新的數組。

3.狀態的類型是普通對象(不包含字符串、數組)

(1)使用ES6Object.assgin方法:

this.setState(preState => ({
  owner: Object.assign({}, preState.owner, {name: 'Tom'});
}))

(2)使用對象擴展語法(object spread properties):

this.setState(preState => ({
  owner: {...preState.owner, name: 'Tom'};
}))

總結一下,建立新的狀態對象的關鍵是,避免使用會直接修改原對象的方法,而是使用能夠返回一個新對象的方法。固然,也可使用一些Immutable的JS庫(如Immutable.js)實現相似的效果。

爲何React推薦組件的狀態是不可變對象呢?一方面是由於對不可變對象的修改會返回一個新對象,不須要擔憂原有對象在不當心的狀況下被修改致使的錯誤,方便程序的管理和調試;另外一方面是出於性能考慮,當對象組件狀態都是不可變對象時,在組件的shouldComponentUpdate方法中僅須要比較先後兩次狀態對象的引用就能夠判斷狀態是否真的改變,從而避免沒必要要的render調用。

相關文章
相關標籤/搜索