摘要:當(dāng)真正執(zhí)行狀態(tài)修改時(shí),依賴的并不能保證是最新的,因?yàn)闀?huì)把多次的修改合并成一次,這時(shí),還是等于這幾次修改發(fā)生前的。下篇預(yù)告深入系列組件的生命周期新書推薦進(jìn)階之路作者徐超畢業(yè)于浙江大學(xué),碩士,資深前端工程師,長期就職于能源物聯(lián)網(wǎng)公司遠(yuǎn)景智能。
文:徐超,《React進(jìn)階之路》作者React 深入系列3:Props 和 State授權(quán)發(fā)布,轉(zhuǎn)載請注明作者及出處
React 深入系列,深入講解了React中的重點(diǎn)概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項(xiàng)目中更加靈活地使用React。
React 的核心思想是組件化的思想,而React 組件的定義可以通過下面的公式描述:
UI = Component(props, state)
組件根據(jù)props和state兩個(gè)參數(shù),計(jì)算得到對應(yīng)界面的UI。可見,props 和 state 是組件的兩個(gè)重要數(shù)據(jù)源。
本篇文章不是對props 和state 基本用法的介紹,而是嘗試從更深層次解釋props 和 state,并且歸納使用它們時(shí)的注意事項(xiàng)。
Props 和 State 本質(zhì)一句話概括,props 是組件對外的接口,state 是組件對內(nèi)的接口。組件內(nèi)可以引用其他組件,組件之間的引用形成了一個(gè)樹狀結(jié)構(gòu)(組件樹),如果下層組件需要使用上層組件的數(shù)據(jù)或方法,上層組件就可以通過下層組件的props屬性進(jìn)行傳遞,因此props是組件對外的接口。組件除了使用上層組件傳遞的數(shù)據(jù)外,自身也可能需要維護(hù)管理數(shù)據(jù),這就是組件對內(nèi)的接口state。根據(jù)對外接口props 和對內(nèi)接口state,組件計(jì)算出對應(yīng)界面的UI。
組件的props 和 state都和組件最終渲染出的UI直接相關(guān)。兩者的主要區(qū)別是:state是可變的,是組件內(nèi)部維護(hù)的一組用于反映組件UI變化的狀態(tài)集合;而props是組件的只讀屬性,組件內(nèi)部不能直接修改props,要想修改props,只能在該組件的上層組件中修改。在組件狀態(tài)上移的場景中,父組件正是通過子組件的props,傳遞給子組件其所需要的狀態(tài)。
如何定義State定義一個(gè)合適的state,是正確創(chuàng)建組件的第一步。state必須能代表一個(gè)組件UI呈現(xiàn)的完整狀態(tài)集,即組件對應(yīng)UI的任何改變,都可以從state的變化中反映出來;同時(shí),state還必須是代表一個(gè)組件UI呈現(xiàn)的最小狀態(tài)集,即state中的所有狀態(tài)都是用于反映組件UI的變化,沒有任何多余的狀態(tài),也不需要通過其他狀態(tài)計(jì)算而來的中間狀態(tài)。
組件中用到的一個(gè)變量是不是應(yīng)該作為組件state,可以通過下面的4條依據(jù)進(jìn)行判斷:
這個(gè)變量是否是通過props從父組件中獲取?如果是,那么它不是一個(gè)狀態(tài)。
這個(gè)變量是否在組件的整個(gè)生命周期中都保持不變?如果是,那么它不是一個(gè)狀態(tài)。
這個(gè)變量是否可以通過state 或props 中的已有數(shù)據(jù)計(jì)算得到?如果是,那么它不是一個(gè)狀態(tài)。
這個(gè)變量是否在組件的render方法中使用?如果不是,那么它不是一個(gè)狀態(tài)。這種情況下,這個(gè)變量更適合定義為組件的一個(gè)普通屬性(除了props 和 state以外的組件屬性 ),例如組件中用到的定時(shí)器,就應(yīng)該直接定義為this.timer,而不是this.state.timer。
請務(wù)必牢記,并不是組件中用到的所有變量都是組件的狀態(tài)!當(dāng)存在多個(gè)組件共同依賴同一個(gè)狀態(tài)時(shí),一般的做法是狀態(tài)上移,將這個(gè)狀態(tài)放到這幾個(gè)組件的公共父組件中。
如何正確修改State 1.不能直接修改State。直接修改state,組件并不會(huì)重新重發(fā)render。例如:
// 錯(cuò)誤 this.state.title = "React";
正確的修改方式是使用setState():
// 正確 this.setState({title: "React"});2. State 的更新是異步的。
調(diào)用setState,組件的state并不會(huì)立即改變,setState只是把要修改的狀態(tài)放入一個(gè)隊(duì)列中,React會(huì)優(yōu)化真正的執(zhí)行時(shí)機(jī),并且React會(huì)出于性能原因,可能會(huì)將多次setState的狀態(tài)修改合并成一次狀態(tài)修改。所以不能依賴當(dāng)前的state,計(jì)算下個(gè)state。當(dāng)真正執(zhí)行狀態(tài)修改時(shí),依賴的this.state并不能保證是最新的state,因?yàn)镽eact會(huì)把多次state的修改合并成一次,這時(shí),this.state還是等于這幾次修改發(fā)生前的state。另外需要注意的是,同樣不能依賴當(dāng)前的props計(jì)算下個(gè)state,因?yàn)閜rops的更新也是異步的。
舉個(gè)例子,對于一個(gè)電商類應(yīng)用,在我們的購物車中,當(dāng)點(diǎn)擊一次購買按鈕,購買的數(shù)量就會(huì)加1,如果我們連續(xù)點(diǎn)擊了兩次按鈕,就會(huì)連續(xù)調(diào)用兩次this.setState({quantity: this.state.quantity + 1}),在React合并多次修改為一次的情況下,相當(dāng)于等價(jià)執(zhí)行了如下代碼:
Object.assign( previousState, {quantity: this.state.quantity + 1}, {quantity: this.state.quantity + 1} )
于是乎,后面的操作覆蓋掉了前面的操作,最終購買的數(shù)量只增加了1個(gè)。
如果你真的有這樣的需求,可以使用另一個(gè)接收一個(gè)函數(shù)作為參數(shù)的setState,這個(gè)函數(shù)有兩個(gè)參數(shù),第一個(gè)參數(shù)是組件的前一個(gè)state(本次組件狀態(tài)修改成功前的state),第二個(gè)參數(shù)是組件當(dāng)前最新的props。如下所示:
// 正確 this.setState((preState, props) => ({ counter: preState.quantity + 1; }))3. State 的更新是一個(gè)淺合并(Shallow Merge)的過程。
當(dāng)調(diào)用setState修改組件狀態(tài)時(shí),只需要傳入發(fā)生改變的狀態(tài)變量,而不是組件完整的state,因?yàn)榻M件state的更新是一個(gè)淺合并(Shallow Merge)的過程。例如,一個(gè)組件的state為:
this.state = { title : "React", content : "React is an wonderful JS library!" }
當(dāng)只需要修改狀態(tài)title時(shí),只需要將修改后的title傳給setState:
this.setState({title: "Reactjs"});
React會(huì)合并新的title到原來的組件state中,同時(shí)保留原有的狀態(tài)content,合并后的state為:
{ title : "Reactjs", content : "React is an wonderful JS library!" }State與Immutable
React官方建議把state當(dāng)作不可變對象,一方面是如果直接修改this.state,組件并不會(huì)重新render;另一方面state中包含的所有狀態(tài)都應(yīng)該是不可變對象。當(dāng)state中的某個(gè)狀態(tài)發(fā)生變化,我們應(yīng)該重新創(chuàng)建一個(gè)新狀態(tài),而不是直接修改原來的狀態(tài)。那么,當(dāng)狀態(tài)發(fā)生變化時(shí),如何創(chuàng)建新的狀態(tài)呢?根據(jù)狀態(tài)的類型,可以分成三種情況:
1. 狀態(tài)的類型是不可變類型(數(shù)字,字符串,布爾值,null, undefined)這種情況最簡單,因?yàn)闋顟B(tài)是不可變類型,直接給要修改的狀態(tài)賦一個(gè)新值即可。如要修改count(數(shù)字類型)、title(字符串類型)、success(布爾類型)三個(gè)狀態(tài):
this.setState({ count: 1, title: "Redux", success: true })2. 狀態(tài)的類型是數(shù)組
如有一個(gè)數(shù)組類型的狀態(tài)books,當(dāng)向books中增加一本書時(shí),使用數(shù)組的concat方法或ES6的數(shù)組擴(kuò)展語法(spread syntax):
// 方法一:使用preState、concat創(chuàng)建新數(shù)組 this.setState(preState => ({ books: preState.books.concat(["React Guide"]); })) // 方法二:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, "React Guide"]; }))
當(dāng)從books中截取部分元素作為新狀態(tài)時(shí),使用數(shù)組的slice方法:
// 使用preState、slice創(chuàng)建新數(shù)組 this.setState(preState => ({ books: preState.books.slice(1,3); }))
當(dāng)從books中過濾部分元素后,作為新狀態(tài)時(shí),使用數(shù)組的filter方法:
// 使用preState、filter創(chuàng)建新數(shù)組 this.setState(preState => ({ books: preState.books.filter(item => { return item != "React"; }); }))
注意不要使用push、pop、shift、unshift、splice等方法修改數(shù)組類型的狀態(tài),因?yàn)檫@些方法都是在原數(shù)組的基礎(chǔ)上修改,而concat、slice、filter會(huì)返回一個(gè)新的數(shù)組。
3. 狀態(tài)的類型是簡單對象(Plain Object)如state中有一個(gè)狀態(tài)owner,結(jié)構(gòu)如下:
this.state = { owner = { name: "老干部", age: 30 } }
當(dāng)修改state時(shí),有如下兩種方式:
1) 使用ES6 的Object.assgin方法
this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: "Jason"}); }))
2) 使用對象擴(kuò)展語法(object spread properties)
this.setState(preState => ({ owner: {...preState.owner, name: "Jason"}; }))
總結(jié)一下,創(chuàng)建新的狀態(tài)的關(guān)鍵是,避免使用會(huì)直接修改原對象的方法,而是使用可以返回一個(gè)新對象的方法。當(dāng)然,也可以使用一些Immutable的JS庫,如Immutable.js,實(shí)現(xiàn)類似的效果。
那么,為什么React推薦組件的狀態(tài)是不可變對象呢?一方面是因?yàn)椴豢勺儗ο蠓奖愎芾砗驼{(diào)試,了解更多可參考這里;另一方面是出于性能考慮,當(dāng)組件狀態(tài)都是不可變對象時(shí),我們在組件的shouldComponentUpdate方法中,僅需要比較狀態(tài)的引用就可以判斷狀態(tài)是否真的改變,從而避免不必要的render方法的調(diào)用。當(dāng)我們使用React 提供的PureComponent時(shí),更是要保證組件狀態(tài)是不可變對象,否則在組件的shouldComponentUpdate方法中,狀態(tài)比較就可能出現(xiàn)錯(cuò)誤。
下篇預(yù)告:React 深入系列4:組件的生命周期
新書推薦《React進(jìn)階之路》
作者:徐超
畢業(yè)于浙江大學(xué),碩士,資深前端工程師,長期就職于能源物聯(lián)網(wǎng)公司遠(yuǎn)景智能。8年軟件開發(fā)經(jīng)驗(yàn),熟悉大前端技術(shù),擁有豐富的Web前端和移動(dòng)端開發(fā)經(jīng)驗(yàn),尤其對React技術(shù)棧和移動(dòng)Hybrid開發(fā)技術(shù)有深入的理解和實(shí)踐經(jīng)驗(yàn)。
美團(tuán)點(diǎn)評廣告平臺(tái)大前端團(tuán)隊(duì)招收20192020年前端實(shí)習(xí)生(偏動(dòng)效方向)
有意者郵件:yao.zhou@meituan.com
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94278.html
摘要:本篇是深入系列的最后一篇,將介紹開發(fā)應(yīng)用時(shí),經(jīng)常用到的模式,這些模式并非都有官方名稱,所以有些模式的命名并不一定準(zhǔn)確,請讀者主要關(guān)注模式的內(nèi)容。 React 深入系列,深入講解了React中的重點(diǎn)概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項(xiàng)目中更加靈活地使用React。 本篇是React深入系列的最后一篇,將介紹開發(fā)React應(yīng)用時(shí),經(jīng)常用到的模式,這些模式并非都有...
摘要:深入系列,深入講解了中的重點(diǎn)概念特性和模式等,旨在幫助大家加深對的理解,以及在項(xiàng)目中更加靈活地使用。下篇預(yù)告深入系列組件的生命周期我的新書進(jìn)階之路已上市,請大家多多支持鏈接京東當(dāng)當(dāng) React 深入系列,深入講解了React中的重點(diǎn)概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項(xiàng)目中更加靈活地使用React。 React 的核心思想是組件化的思想,而React 組件的定...
摘要:無狀態(tài)組件和有狀態(tài)組件無狀態(tài)組件和有狀態(tài)組件,劃分依據(jù)是根據(jù)組件內(nèi)部是否維護(hù)。展示型組件和容器型組件展示型組件和容器型組件,劃分依據(jù)是根據(jù)組件的職責(zé)。 文:徐超,《React進(jìn)階之路》作者授權(quán)發(fā)布,轉(zhuǎn)載請注明作者及出處 React 深入系列2:組件分類 React 深入系列,深入講解了React中的重點(diǎn)概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項(xiàng)目中更加靈活地使...
摘要:無狀態(tài)組件和有狀態(tài)組件無狀態(tài)組件和有狀態(tài)組件,劃分依據(jù)是根據(jù)組件內(nèi)部是否維護(hù)。展示型組件和容器型組件展示型組件和容器型組件,劃分依據(jù)是根據(jù)組件的職責(zé)。 React 深入系列,深入講解了React中的重點(diǎn)概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項(xiàng)目中更加靈活地使用React。 React 組件有很多種分類方式,常見的分類方式有函數(shù)組件和類組件,無狀態(tài)組件和有狀態(tài)組件...
摘要:使用匿名函數(shù)先上代碼代碼點(diǎn)擊的事件響應(yīng)函數(shù)是一個(gè)匿名函數(shù),這應(yīng)該是最常見的處理事件響應(yīng)的方式了。事件響應(yīng)函數(shù)的傳參問題事件響應(yīng)函數(shù)默認(rèn)是會(huì)被傳入一個(gè)事件對象作為參數(shù)的。關(guān)于事件響應(yīng)函數(shù),還有一個(gè)地方需要注意。 React 深入系列,深入講解了React中的重點(diǎn)概念、特性和模式等,旨在幫助大家加深對React的理解,以及在項(xiàng)目中更加靈活地使用React。 Web應(yīng)用中,事件處理是重要的一...
閱讀 2755·2019-08-30 15:53
閱讀 521·2019-08-29 17:22
閱讀 1040·2019-08-29 13:10
閱讀 2307·2019-08-26 13:45
閱讀 2751·2019-08-26 10:46
閱讀 3202·2019-08-26 10:45
閱讀 2504·2019-08-26 10:14
閱讀 467·2019-08-23 18:23