摘要:深入系列,深入講解了中的重點概念特性和模式等,旨在幫助大家加深對的理解,以及在項目中更加靈活地使用。下篇預告深入系列組件的生命周期我的新書進階之路已上市,請大家多多支持鏈接京東當當
React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。
React 的核心思想是組件化的思想,而React 組件的定義可以通過下面的公式描述:
UI = Component(props, state)
組件根據props和state兩個參數,計算得到對應界面的UI??梢?,props 和 state 是組件的兩個重要數據源。
本篇文章不是對props 和state 基本用法的介紹,而是嘗試從更深層次解釋props 和 state,并且歸納使用它們時的注意事項。
Props 和 State 本質一句話概括,props 是組件對外的接口,state 是組件對內的接口。組件內可以引用其他組件,組件之間的引用形成了一個樹狀結構(組件樹),如果下層組件需要使用上層組件的數據或方法,上層組件就可以通過下層組件的props屬性進行傳遞,因此props是組件對外的接口。組件除了使用上層組件傳遞的數據外,自身也可能需要維護管理數據,這就是組件對內的接口state。根據對外接口props 和對內接口state,組件計算出對應界面的UI。
組件的props 和 state都和組件最終渲染出的UI直接相關。兩者的主要區別是:state是可變的,是組件內部維護的一組用于反映組件UI變化的狀態集合;而props是組件的只讀屬性,組件內部不能直接修改props,要想修改props,只能在該組件的上層組件中修改。在組件狀態上移的場景中,父組件正是通過子組件的props,傳遞給子組件其所需要的狀態。
如何定義State定義一個合適的state,是正確創建組件的第一步。state必須能代表一個組件UI呈現的完整狀態集,即組件對應UI的任何改變,都可以從state的變化中反映出來;同時,state還必須是代表一個組件UI呈現的最小狀態集,即state中的所有狀態都是用于反映組件UI的變化,沒有任何多余的狀態,也不需要通過其他狀態計算而來的中間狀態。
組件中用到的一個變量是不是應該作為組件state,可以通過下面的4條依據進行判斷:
這個變量是否是通過props從父組件中獲取?如果是,那么它不是一個狀態。
這個變量是否在組件的整個生命周期中都保持不變?如果是,那么它不是一個狀態。
這個變量是否可以通過state 或props 中的已有數據計算得到?如果是,那么它不是一個狀態。
這個變量是否在組件的render方法中使用?如果不是,那么它不是一個狀態。這種情況下,這個變量更適合定義為組件的一個普通屬性(除了props 和 state以外的組件屬性 ),例如組件中用到的定時器,就應該直接定義為this.timer,而不是this.state.timer。
請務必牢記,并不是組件中用到的所有變量都是組件的狀態!當存在多個組件共同依賴同一個狀態時,一般的做法是狀態上移,將這個狀態放到這幾個組件的公共父組件中。
如何正確修改State直接修改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!" }State與Immutable
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進階之路》已上市,請大家多多支持!
鏈接:京東 當當
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/95200.html
摘要:當真正執行狀態修改時,依賴的并不能保證是最新的,因為會把多次的修改合并成一次,這時,還是等于這幾次修改發生前的。下篇預告深入系列組件的生命周期新書推薦進階之路作者徐超畢業于浙江大學,碩士,資深前端工程師,長期就職于能源物聯網公司遠景智能。 文:徐超,《React進階之路》作者授權發布,轉載請注明作者及出處 React 深入系列3:Props 和 State React 深入系列,深...
摘要:本篇是深入系列的最后一篇,將介紹開發應用時,經常用到的模式,這些模式并非都有官方名稱,所以有些模式的命名并不一定準確,請讀者主要關注模式的內容。 React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。 本篇是React深入系列的最后一篇,將介紹開發React應用時,經常用到的模式,這些模式并非都有...
摘要:使用匿名函數先上代碼代碼點擊的事件響應函數是一個匿名函數,這應該是最常見的處理事件響應的方式了。事件響應函數的傳參問題事件響應函數默認是會被傳入一個事件對象作為參數的。關于事件響應函數,還有一個地方需要注意。 React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。 Web應用中,事件處理是重要的一...
摘要:無狀態組件和有狀態組件無狀態組件和有狀態組件,劃分依據是根據組件內部是否維護。展示型組件和容器型組件展示型組件和容器型組件,劃分依據是根據組件的職責。 文:徐超,《React進階之路》作者授權發布,轉載請注明作者及出處 React 深入系列2:組件分類 React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使...
摘要:無狀態組件和有狀態組件無狀態組件和有狀態組件,劃分依據是根據組件內部是否維護。展示型組件和容器型組件展示型組件和容器型組件,劃分依據是根據組件的職責。 React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項目中更加靈活地使用React。 React 組件有很多種分類方式,常見的分類方式有函數組件和類組件,無狀態組件和有狀態組件...
閱讀 2860·2019-08-30 15:44
閱讀 1888·2019-08-29 13:59
閱讀 2845·2019-08-29 12:29
閱讀 1090·2019-08-26 13:57
閱讀 3202·2019-08-26 13:45
閱讀 3330·2019-08-26 10:28
閱讀 825·2019-08-26 10:18
閱讀 1695·2019-08-23 16:52