摘要:所有派生狀態(tài)導(dǎo)致的問題無異于兩種無條件的根據(jù)來更新無論和是否匹配來更新。派生狀態(tài)最常見的錯(cuò)誤就是將這兩者混和在一起。因此通常被用于性能優(yōu)化而不是來判斷派生狀態(tài)的正確性。我們可以使用派生狀態(tài)來存儲(chǔ)過濾列表這種方式避免了重新計(jì)算。
原文鏈接:https://reactjs.org/blog/2018...
翻譯這篇文章的起因是因?yàn)樵谝淮涡枨蟮绣e(cuò)誤的使用了getDerivedStateFromProps這個(gè)生命周期導(dǎo)致子組件的state被循環(huán)重置,于是翻到了這篇文章,然后就開啟的翻譯之旅。
在很長(zhǎng)一段時(shí)間,生命周期componentWillReceiveProps是用來響應(yīng)props更新來改變state并不需要額外渲染的唯一方法。在16.3版本中,我們提供了getDerivedStateFromProps這個(gè)更安全生命周期來解決相同的用例。同時(shí),我們發(fā)現(xiàn)人們對(duì)于如何使用這兩種方式有很多誤解,并且我們發(fā)現(xiàn)了一些造成微妙和令人混淆的反模式。在16.4中的getDerivedStateFromProps的bug修復(fù)使得派生狀態(tài)更加可預(yù)測(cè),且更容易讓人注意到錯(cuò)誤使用它的結(jié)果。
什么時(shí)候去使用派生狀態(tài)getDerivedStateFromProps的存在只有一個(gè)目的。它可以使組件根據(jù)props的改變來更新內(nèi)部的state。我們之間的博客提供了一些例子:通過改變offset的prop來改變當(dāng)前的滾動(dòng)方向和加載通過source props所指定的外部數(shù)據(jù)。
我們沒有提供更多的例子,因?yàn)樽鳛橐粋€(gè)基本的規(guī)則,派生狀態(tài)應(yīng)該被謹(jǐn)慎的使用。所有派生狀態(tài)導(dǎo)致的問題無異于兩種:(1)無條件的根據(jù)props來更新state(2)無論props和state是否匹配來更新state。
如果僅用派生狀態(tài)來記錄一些基于當(dāng)前props的計(jì)算,則不需要派生狀態(tài);
如果你無條件的更新派生狀態(tài),或者無論props和state是否匹配來更新state,你的組件將會(huì)過于頻繁的去重置狀態(tài);
使用派生狀態(tài)的常見問題“受控的”和“不受控的”通常用來指表單的輸入,但它也同樣可以表示任何組件數(shù)據(jù)所在的位置。數(shù)據(jù)通過props傳來被認(rèn)為是“受控的”(因?yàn)楦附M件在控制著這個(gè)數(shù)據(jù))。數(shù)據(jù)僅存在其內(nèi)部的state中被認(rèn)為是“不受控的”(因?yàn)槠涓附M件不能直接的改變這它)。
派生狀態(tài)最常見的錯(cuò)誤就是將這兩者混和在一起。當(dāng)一個(gè)派生狀態(tài)的值同樣通過setState的調(diào)用來更新時(shí),這就無法保證數(shù)據(jù)有單一的真實(shí)來源。這也許和上面提到的外部數(shù)據(jù)加載的例子很相似,但他們?cè)谝恍┲匾姆矫嫔鲜遣煌摹T诩虞d的例子中,”source“的props和”loading“的state都有一個(gè)明確的真實(shí)來源。當(dāng)source props改變的時(shí)候,應(yīng)該總是覆蓋loading state。相反,只有props改變且由組件管理的時(shí)候,才去重寫state。
當(dāng)這些約束中的任何一個(gè)被改變時(shí)將會(huì)出現(xiàn)問題。通常有兩種形式,讓我們接下來看一下這兩種形式。
反模式:無條件的從prop復(fù)制狀態(tài)到state一個(gè)常見的誤解是getDerivedStateFromProps和componentWillReceiveProps只有在props改變的時(shí)候會(huì)被調(diào)用。這兩個(gè)生命周期將會(huì)在父組件重新渲染的任何時(shí)間被調(diào)用,而不管props是否與之前不同。因此,在使用這兩個(gè)生命周期時(shí),無條件的覆蓋state總是不安全的,將會(huì)導(dǎo)致state更新時(shí)的丟失。
讓我們考慮一個(gè)例子來說明這個(gè)問題。
class EmailInput extends Component { state = { email: this.props.email }; render() { return ; } handleChange = event => { this.setState({ email: event.target.value }); }; componentWillReceiveProps(nextProps) { // 這里將會(huì)覆蓋任何本地state的更新。 this.setState({ email: nextProps.email }); } }
這個(gè)組件看起來可能沒有問題,state由prop傳來的數(shù)據(jù)初始化,且當(dāng)我們改變input的值時(shí)state被更新。但是當(dāng)我們的父組件重新渲染的時(shí)候,任何我們?cè)趇nput中輸入的狀態(tài)都將丟失,即使我們?nèi)ケ容^nextProps.email !== this.state.email也是如此。
在這個(gè)例子中,只有當(dāng)email的prop的改變的時(shí)候添加shouldComponentUpdate來重新渲染可以解決這個(gè)問題,但是實(shí)際上,組件通常會(huì)接收多個(gè)prop,另一個(gè)prop的改變?nèi)稳粫?huì)造成組件的重新渲染和不正確的重置。此外函數(shù)和對(duì)象的prop通常也會(huì)是內(nèi)聯(lián)創(chuàng)建的,這也會(huì)使shouldComponentUpdate正確的返回true變得困難。這里有一個(gè)例子。因此shouldComponentUpdate通常被用于性能優(yōu)化而不是來判斷派生狀態(tài)的正確性。
希望到現(xiàn)在大家清楚為什么不要無條件的復(fù)制props到state。在我們找到可能的解決方案之前,讓我們?nèi)タ匆粋€(gè)與之相關(guān)的問題:如果只在props.email改變的時(shí)候去更新state會(huì)怎樣?
反模式:props改變的時(shí)候清除state繼續(xù)上面的例子,我們可以避免在props.email更改時(shí)意外的清除state:
class EmailInput extends Component { state = { email: this.props.email }; componentWillReceiveProps(nextProps) { // 任何時(shí)候props.email改變,更新state. if (nextProps.email !== this.props.email) { this.setState({ email: nextProps.email }); } } // ... }
我們?nèi)〉昧撕艽蟮倪M(jìn)步,現(xiàn)在我們的組件只有在props真正改變的時(shí)候才會(huì)清除state。
還有一個(gè)微妙的問題,想象一下使用以上的組件來構(gòu)建密碼管理應(yīng)用。當(dāng)使用同一個(gè)email在兩個(gè)賬戶的詳情頁(yè)導(dǎo)航時(shí),input將會(huì)無法重置,這是因?yàn)閭鬟f給組件的props相對(duì)于兩個(gè)賬號(hào)來說時(shí)相同的。這對(duì)用戶來說將會(huì)是一個(gè)驚喜,因?yàn)閷?duì)一個(gè)賬戶的未保存更改會(huì)錯(cuò)誤的影響到另一個(gè)賬戶。查看演示。
這種設(shè)計(jì)從本質(zhì)上來說是錯(cuò)誤的,但卻是一個(gè)很容易犯的錯(cuò)誤,幸運(yùn)的是,有兩種更好的選擇,這兩者的關(guān)鍵在于,對(duì)于任何數(shù)據(jù)片段,你都需要選擇一個(gè)將它作為數(shù)據(jù)源的組件,而避免在其它組件重復(fù)使用。
首選方案 推薦:完全受控組件避免上述問題的一個(gè)方案是完全移除組建中的state,如果email僅作為props存在,那我們將不必?fù)?dān)心它和state沖突,我們甚至可以講EmailInput組件變?yōu)楦p量級(jí)的function組件:
function EmailInput(props) { return ; }
這種方法簡(jiǎn)化了組件的實(shí)現(xiàn),但如果你仍需要存儲(chǔ)一個(gè)草稿值,那么父表單組件現(xiàn)在需要手動(dòng)執(zhí)行該操作。查看演示。
推薦:帶有key的完全不受控組件另一個(gè)方法是讓我們的組件完全擁有“草稿”email的state,這時(shí)我們的組件仍然可以接收props來作為初始值,但是它會(huì)忽略props的后續(xù)更改。
class EmailInput extends Component { state = { email: this.props.defaultEmail }; handleChange = event => { this.setState({ email: event.target.value }); }; render() { return ; } }
為了在移動(dòng)到其他項(xiàng)目時(shí)重置值(如密碼管理器場(chǎng)景中),可以使用一個(gè)React的特殊屬性key。當(dāng)一個(gè)key改變的時(shí)候,React會(huì)創(chuàng)建一個(gè)新的組件實(shí)例而不是更新當(dāng)前的組件,key通常被用在動(dòng)態(tài)的list但是同樣可以在這里使用。
每當(dāng)id改變的時(shí)候,EmailInput組件將會(huì)被重新創(chuàng)建,它的state將會(huì)被重置為最后一次的defaultEmail的值。查看演示。使用此方法,你講不用在每一個(gè)input上添加key,把一個(gè)key放在整個(gè)form上更有意義,每當(dāng)key改變的時(shí)候,表單中的input都會(huì)重置到其初始狀態(tài)。
替代方案1:通過ID prop來重置不受控組件如果key在某些場(chǎng)合不適用(也許初始化對(duì)于組件來說是昂貴的),一個(gè)可行但繁瑣的方式是在getDerivedStateFromProps中去監(jiān)測(cè)userID:
class EmailInput extends Component { state = { email: this.props.defaultEmail, prevPropsUserID: this.props.userID }; static getDerivedStateFromProps(props, state) { if (props.userID !== state.prevPropsUserID) { return { prevPropsUserID: props.userID, email: props.defaultEmail }; } return null; } // ... }
這也提供了靈活性,如果我們選擇,只重置組件內(nèi)的部分state。查看演示。
替代方案2:通過實(shí)例方法來重置不受控組件如果沒有合適的id來作為key但是又要重置狀態(tài),一種解決方案是為組件生成一個(gè)隨機(jī)數(shù)或者自動(dòng)遞增值來作為key,另一種方案是通過實(shí)例的方法來強(qiáng)制重置組件的state。
class EmailInput extends Component { state = { email: this.props.defaultEmail }; resetEmailForNewUser(newEmail) { this.setState({ email: newEmail }); } // ... }
父組件將通過ref拿到組件的實(shí)例從而調(diào)用該方法。查看演示。
在某些場(chǎng)景下ref會(huì)很有用,但是我們建議你謹(jǐn)慎的使用它,即使在demo中,這個(gè)方法也是最不理想的,因?yàn)閷?huì)造成兩次渲染而不是一個(gè)。
總結(jié)總而言之,當(dāng)設(shè)計(jì)一個(gè)組件時(shí),一個(gè)重要的方面是它的數(shù)據(jù)是可控的還是不可控的。
盡量避免在state中去“鏡像”一個(gè)props值,使這個(gè)組件成為受控組件,在父組件的state中去合并這兩個(gè)state。例如,與其在組件中去接受一個(gè)committed的props并且跟蹤一個(gè)draft的state,不如讓父組件去同時(shí)管理這個(gè)state.draftValue和state.committedValue并直接控制子組件,這將使組件更加的明確和可預(yù)測(cè)。
對(duì)于一個(gè)不受控組件,如果你想根據(jù)一個(gè)props的改變來重置state,你需要遵循以下幾點(diǎn):
首選:要重置全部?jī)?nèi)部state,使用key屬性;
備選1:如果只重置部分state,監(jiān)測(cè)props中屬性的變化;
備選2:還可以考慮通過ref調(diào)用實(shí)力的方法;
memoization怎樣?我們還看到了派生狀態(tài)用于確保渲染中使用的昂貴值僅在輸入發(fā)生變化時(shí)才會(huì)重新計(jì)算,這種技術(shù)叫做memoization
使用派生狀態(tài)來做memoization不一定是壞事,但通常不是最好的解決辦法。派生狀態(tài)的管理存在一定的復(fù)雜性,并且這種復(fù)雜性隨著屬性的增加而增加。例如,如果我們向組件的state添加第二個(gè)派生字段,那么我們的實(shí)現(xiàn)將需要分別跟蹤對(duì)兩個(gè)字段的更改。
讓我們看一個(gè)組件的示例,該組件使用一個(gè)prop(項(xiàng)目列表)并呈現(xiàn)與用戶輸入的搜索查詢匹配的項(xiàng)。我們可以使用派生狀態(tài)來存儲(chǔ)過濾列表:
class Example extends Component { state = { filterText: "", }; // ******************************************************* // NOTE: this example is NOT the recommended approach. // See the examples below for our recommendations instead. // ******************************************************* static getDerivedStateFromProps(props, state) { // Re-run the filter whenever the list array or filter text change. // Note we need to store prevPropsList and prevFilterText to detect changes. if ( props.list !== state.prevPropsList || state.prevFilterText !== state.filterText ) { return { prevPropsList: props.list, prevFilterText: state.filterText, filteredList: props.list.filter(item => item.text.includes(state.filterText)) }; } return null; } handleChange = event => { this.setState({ filterText: event.target.value }); }; render() { return (); } } {this.state.filteredList.map(item =>
- {item.text}
)}
這種方式避免了重新計(jì)算filteredList。但是他比我們需要的更加的復(fù)雜,因?yàn)樗枰謩e的跟蹤和檢查我們的props和state以便能夠正確的更新列表。在下面這個(gè)例子中,我們通過PureComponent并將filter操作放到render中來簡(jiǎn)化操作:
// PureComponents只有在至少一個(gè)state或者prop改變的時(shí)候才會(huì)重新渲染 // 通過對(duì)state和props的keys的淺比較來確認(rèn)改變。 class Example extends PureComponent { state = { filterText: "" }; handleChange = event => { this.setState({ filterText: event.target.value }); }; render() { // 只有props.list 或 state.filterText 改變的時(shí)候PureComponent的render才會(huì)調(diào)用 const filteredList = this.props.list.filter( item => item.text.includes(this.state.filterText) ) return (); } } {filteredList.map(item =>
- {item.text}
)}
上述例子比派生狀態(tài)的版本更加的干凈和簡(jiǎn)潔,但是有些時(shí)候這可能還不夠好,例如對(duì)于大型列表來說,過濾可能很慢,且如果有其他的props改變PureComponent也不會(huì)阻止其重新渲染。為了解決這兩個(gè)問題,我們可以添加一個(gè)memoization,以避免不必要地重新過濾我們的列表:
import memoize from "memoize-one"; class Example extends Component { state = { filterText: "" }; filter = memoize( (list, filterText) => list.filter(item => item.text.includes(filterText)) ); handleChange = event => { this.setState({ filterText: event.target.value }); }; render() { const filteredList = this.filter(this.props.list, this.state.filterText); return (); } } {filteredList.map(item =>
- {item.text}
)}
當(dāng)使用memoization時(shí),有以下約束:
在大多數(shù)情況下,您需要將memoized函數(shù)附加到組件實(shí)例。這可以防止組件的多個(gè)實(shí)例重置彼此的memoized key。
通常情況下,您需要使用具有有限緩存大小的memoization,以防止內(nèi)存泄漏。(在上面的例子中,我們使用了memoize-one,因?yàn)樗痪彺孀罱膮?shù)和結(jié)果。)
如果每次父組件呈現(xiàn)時(shí)重新創(chuàng)建props.list,本節(jié)中顯示的實(shí)現(xiàn)都不會(huì)起作用。但在大多數(shù)情況下,這種設(shè)置是合適的。
最后在實(shí)際應(yīng)用中,組件通常包含受控和不受控制行為混合。沒關(guān)系,如果每個(gè)值都有明確的來源,則可以避免上面提到的反模式。
值得重新思考的是,getDerivedStateFromProps(以及通常的派生狀態(tài))是一種高級(jí)功能,應(yīng)該謹(jǐn)慎使用。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/99313.html
摘要:譯者前端小智原文就像人們對(duì)更新移動(dòng)應(yīng)用程序和操作系統(tǒng)感到興奮一樣,開發(fā)人員也應(yīng)該對(duì)更新框架感到興奮。錯(cuò)誤邊界是一種組件。注意將作為值傳遞進(jìn)去并不會(huì)導(dǎo)致使用。如果兩者不同,則返回一個(gè)用于更新狀態(tài)的對(duì)象,否則就返回,表示不需要更新狀態(tài)。 譯者:前端小智 原文:medium.freecodecamp.org/why-react16… 就像人們對(duì)更新移動(dòng)應(yīng)用程序和操作系統(tǒng)感到興奮一樣,開發(fā)人員也應(yīng)...
摘要:我現(xiàn)在寫的這些是為了解決和這兩個(gè)狀態(tài)管理庫(kù)之間的困惑。這甚至是危險(xiǎn)的,因?yàn)檫@部分人將無法體驗(yàn)和這些庫(kù)所要解決的問題。這肯定是要第一時(shí)間解決的問題。函數(shù)式編程是不斷上升的范式,但對(duì)于大部分開發(fā)者來說是新奇的。規(guī)模持續(xù)增長(zhǎng)的應(yīng) 原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用...
摘要:開閉原則軟件實(shí)體類,模塊,函數(shù)應(yīng)該是可以擴(kuò)展的,而不是修改。函數(shù)并不符合開閉原則,因?yàn)橐坏┯行聞?dòng)物出現(xiàn),它需要修改代碼。 By Chidume Nnamdi | Oct 9, 2018 原文 面向?qū)ο蟮木幊填愋蜑檐浖_發(fā)帶來了新的設(shè)計(jì)。 這使開發(fā)人員能夠在一個(gè)類中組合具有相同目的/功能的數(shù)據(jù),來實(shí)現(xiàn)單獨(dú)的一個(gè)功能,不必關(guān)心整個(gè)應(yīng)用程序如何。 但是,這種面向?qū)ο蟮木幊踢€是會(huì)讓開發(fā)者困惑或...
繼承 在前面的課程中,你已經(jīng)多次看到了繼承,在Java語言中,類可以從其他類派生,從而從這些類繼承字段和方法。 定義:從另一個(gè)類派生的類稱為子類(也是派生類,擴(kuò)展類或子類),派生子類的類稱為超類(也是基類或父類)。 除了Object沒有超類,每個(gè)類都有一個(gè)且只有一個(gè)直接超類(單繼承),在沒有任何其他顯式超類的情況下,每個(gè)類都隱式地是Object的子類。 類可以從派生自類的類派生的類派生,依此類推,...
閱讀 3627·2023-04-26 02:32
閱讀 3904·2021-11-23 10:05
閱讀 2291·2021-10-08 10:04
閱讀 2711·2021-09-22 16:06
閱讀 3612·2021-09-22 15:27
閱讀 764·2019-08-30 15:54
閱讀 1698·2019-08-30 13:50
閱讀 2704·2019-08-29 13:56