文:徐超,《React進階之路》做者html
受權發佈,轉載請註明做者及出處前端
React 深刻系列,深刻講解了React中的重點概念、特性和模式等,旨在幫助你們加深對React的理解,以及在項目中更加靈活地使用React。git
React 的核心思想是組件化的思想,而React 組件的定義能夠經過下面的公式描述:github
UI = Component(props, state)
複製代碼
組件根據props和state兩個參數,計算獲得對應界面的UI。可見,props 和 state 是組件的兩個重要數據源。redux
本篇文章不是對props 和state 基本用法的介紹,而是嘗試從更深層次解釋props 和 state,而且概括使用它們時的注意事項。數組
**一句話歸納,props 是組件對外的接口,state 是組件對內的接口。**組件內能夠引用其餘組件,組件之間的引用造成了一個樹狀結構(組件樹),若是下層組件須要使用上層組件的數據或方法,上層組件就能夠經過下層組件的props屬性進行傳遞,所以props是組件對外的接口。組件除了使用上層組件傳遞的數據外,自身也可能須要維護管理數據,這就是組件對內的接口state。根據對外接口props 和對內接口state,組件計算出對應界面的UI。bash
組件的props 和 state都和組件最終渲染出的UI直接相關。二者的主要區別是:state是可變的,是組件內部維護的一組用於反映組件UI變化的狀態集合;而props是組件的只讀屬性,組件內部不能直接修改props,要想修改props,只能在該組件的上層組件中修改。在組件狀態上移的場景中,父組件正是經過子組件的props,傳遞給子組件其所須要的狀態。markdown
定義一個合適的state,是正確建立組件的第一步。state必須能表明一個組件UI呈現的完整狀態集,即組件對應UI的任何改變,均可以從state的變化中反映出來;同時,state還必須是表明一個組件UI呈現的最小狀態集,即state中的全部狀態都是用於反映組件UI的變化,沒有任何多餘的狀態,也不須要經過其餘狀態計算而來的中間狀態。前端工程師
組件中用到的一個變量是否是應該做爲組件state,能夠經過下面的4條依據進行判斷:ecmascript
請務必牢記,並非組件中用到的全部變量都是組件的狀態!當存在多個組件共同依賴同一個狀態時,通常的作法是狀態上移,將這個狀態放到這幾個組件的公共父組件中。
直接修改state,組件並不會從新重發render。例如:
// 錯誤 this.state.title = 'React'; 複製代碼
正確的修改方式是使用setState()
:
// 正確 this.setState({title: 'React'}); 複製代碼
調用setState
,組件的state並不會當即改變,setState
只是把要修改的狀態放入一個隊列中,React會優化真正的執行時機,而且React會出於性能緣由,可能會將屢次setState
的狀態修改合併成一次狀態修改。因此不能依賴當前的state,計算下個state。當真正執行狀態修改時,依賴的this.state並不能保證是最新的state,由於React會把屢次state的修改合併成一次,這時,this.state仍是等於這幾回修改發生前的state。另外須要注意的是,一樣不能依賴當前的props計算下個state,由於props的更新也是異步的。
舉個例子,對於一個電商類應用,在咱們的購物車中,當點擊一次購買按鈕,購買的數量就會加1,若是咱們連續點擊了兩次按鈕,就會連續調用兩次this.setState({quantity: this.state.quantity + 1})
,在React合併屢次修改成一次的狀況下,至關於等價執行了以下代碼:
Object.assign(
previousState,
{quantity: this.state.quantity + 1},
{quantity: this.state.quantity + 1}
)
複製代碼
因而乎,後面的操做覆蓋掉了前面的操做,最終購買的數量只增長了1個。
若是你真的有這樣的需求,可使用另外一個接收一個函數做爲參數的setState
,這個函數有兩個參數,第一個參數是組件的前一個state(本次組件狀態修改爲功前的state),第二個參數是組件當前最新的props。以下所示:
// 正確
this.setState((preState, props) => ({
counter: preState.quantity + 1;
}))
複製代碼
當調用setState
修改組件狀態時,只須要傳入發生改變的狀態變量,而不是組件完整的state,由於組件state的更新是一個淺合併(Shallow Merge)的過程。例如,一個組件的state爲:
this.state = { title : 'React', content : 'React is an wonderful JS library!' } 複製代碼
當只須要修改狀態title
時,只須要將修改後的title
傳給setState
:
this.setState({title: 'Reactjs'}); 複製代碼
React會合並新的title
到原來的組件state中,同時保留原有的狀態content
,合併後的state爲:
{ title : 'Reactjs', content : 'React is an wonderful JS library!' } 複製代碼
React官方建議把state看成不可變對象,一方面是若是直接修改this.state,組件並不會從新render;另外一方面state中包含的全部狀態都應該是不可變對象。當state中的某個狀態發生變化,咱們應該從新建立一個新狀態,而不是直接修改原來的狀態。那麼,當狀態發生變化時,如何建立新的狀態呢?根據狀態的類型,能夠分紅三種狀況:
這種狀況最簡單,由於狀態是不可變類型,直接給要修改的狀態賦一個新值便可。如要修改count(數字類型)、title(字符串類型)、success(布爾類型)三個狀態:
this.setState({ count: 1, title: 'Redux', success: true }) 複製代碼
若有一個數組類型的狀態books,當向books中增長一本書時,使用數組的concat方法或ES6的數組擴展語法(spread syntax):
// 方法一:使用preState、concat建立新數組 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法二:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; })) 複製代碼
當從books中截取部分元素做爲新狀態時,使用數組的slice方法:
// 使用preState、slice建立新數組
this.setState(preState => ({
books: preState.books.slice(1,3);
}))
複製代碼
當從books中過濾部分元素後,做爲新狀態時,使用數組的filter方法:
// 使用preState、filter建立新數組 this.setState(preState => ({ books: preState.books.filter(item => { return item != 'React'; }); })) 複製代碼
注意不要使用push、pop、shift、unshift、splice等方法修改數組類型的狀態,由於這些方法都是在原數組的基礎上修改,而concat、slice、filter會返回一個新的數組。
如state中有一個狀態owner,結構以下:
this.state = { owner = { name: '老幹部', age: 30 } } 複製代碼
當修改state時,有以下兩種方式:
1) 使用ES6 的Object.assgin方法
this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); })) 複製代碼
2) 使用對象擴展語法(object spread properties)
this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; })) 複製代碼
總結一下,建立新的狀態的關鍵是,避免使用會直接修改原對象的方法,而是使用能夠返回一個新對象的方法。固然,也可使用一些Immutable的JS庫,如Immutable.js,實現相似的效果。
那麼,爲何React推薦組件的狀態是不可變對象呢?一方面是由於不可變對象方便管理和調試,瞭解更多可參考這裏;另外一方面是出於性能考慮,當組件狀態都是不可變對象時,咱們在組件的shouldComponentUpdate
方法中,僅須要比較狀態的引用就能夠判斷狀態是否真的改變,從而避免沒必要要的render
方法的調用。當咱們使用React 提供的PureComponent
時,更是要保證組件狀態是不可變對象,不然在組件的shouldComponentUpdate
方法中,狀態比較就可能出現錯誤。
React 深刻系列4:組件的生命週期
新書推薦《React進階之路》
做者:徐超
畢業於浙江大學,碩士,資深前端工程師,長期就任於能源物聯網公司遠景智能。8年軟件開發經驗,熟悉大前端技術,擁有豐富的Web前端和移動端開發經驗,尤爲對React技術棧和移動Hybrid開發技術有深刻的理解和實踐經驗。
2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!