組件state
必須能表明一個組件UI呈現的完整狀態集,即組件的任何UI改變均可以從state
的變化中反映出來;同時,state
還必須表明一個組件UI呈現的最小狀態集,即state
中的全部狀態都用於反映組件UI的變化,沒有任何多餘的狀態,也不該該存在經過其餘狀態計算而來的中間狀態。
首先,什麼是普通屬性?數組
咱們的組件都是使用ES6
的class
定義的,因此組件的屬性其實也就是class
的屬性(更確切的說法是class
實例化對象的屬性,但由於JavaScript本質上是沒有類的定義的,class
只不過是ES6
提供的語法糖,因此這裏模糊化類和對象的區別)。異步
在ES6
中,可使用this.{屬性名}
定義一個class
的屬性,也能夠說屬性是直接掛載到this
下的變量。所以,state
、props
實際上也是組件的屬性,只不過它們是React
爲咱們在Component class
中預約義好的屬性。除了state
、props
之外的其餘組件屬性稱爲組件的普通屬性。函數
好比,組件中須要一個定時器來自動更新顯示時間時,咱們都會須要一個timerId
來保存該定時器,已在須要的可以清除它。這就是一個普通的屬性,由於它和組件要渲染的內容沒有直接關係。性能
所以,當咱們在組件中須要用到一個變量,而且它與組件的渲染無關時,就應該把這個變量定義爲組件的普通屬性,直接掛載到this
下,而不是做爲組件的state
。或者更直觀的判斷是,看組件render方法中是否使用到了這個變量,若是沒有,它就是一個普通屬性
。優化
state
和props
又有什麼區別呢?this
state
和props
都直接和組件的UI渲染有關,它們的變化都會觸發組件從新渲染,但props
對於使用它的組件來講是隻讀的,是經過父組件傳遞過來的,要想修改props
,只能在父組件中修改;而state
是組件內部本身維護的狀態,是可變的。調試
其實區分state
和props
的關鍵就是,‘控制權’是在組件自身,仍是由其父組件來控制的。code
(1)這個變量是否經過props
從父組件中獲取?若是是,那麼它不是一個狀態。對象
(2)這個變量是否在組件的整個生命週期中都保持不變?若是是,那麼它不是一個狀態。生命週期
(3)這個變量是否能夠經過其餘狀態(state)
或者屬性(props)
計算獲得?若是是,那麼它不是一個狀態。
(4)這個變量是否在組件的render
方法中使用?若是不是,那麼它不是一個狀態。這種狀況下,這個變量更適合定義爲組件的一個普通屬性。
一、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
的值。
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)使用ES6
的Object.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
調用。