摘要:本文用于闡述模式的算法和數(shù)學(xué)背景,以及解釋了它為什么是里最完美的狀態(tài)管理實(shí)現(xiàn)。歡迎大家討論和發(fā)表意見。
本文用于闡述StateUp模式的算法和數(shù)學(xué)背景,以及解釋了它為什么是React里最完美的狀態(tài)管理實(shí)現(xiàn)。
關(guān)于StateUp模式請(qǐng)參閱:https://segmentfault.com/a/11...
P-State, V-State如果要做組件的態(tài)封裝,從組件內(nèi)部看,存在兩種不同的state:
p-state, or persistent state, 是生命周期超過組件本身的state,即使組件從DOM上銷毀,這些state仍然需要在組件外部持久化;
v-state, or volatile state, 是生命周期和組件一樣的state,如果組件從DOM上銷毀,這些state一起銷毀;
根據(jù)這個(gè)定義,React組件的this.state毫無疑問是v-state;
開發(fā)者常說的model或者store state應(yīng)該看作p-state,但是這樣說過于籠統(tǒng)和寬泛,沒有邊界;而另一個(gè)說法,view state,同樣缺乏明確的邊界定義;所以我們暫時(shí)避免使用這兩種表述;用具有嚴(yán)格定義的p-state和v-state來展開討論;
責(zé)任與邊界對(duì)象封裝的責(zé)任與邊界在面向?qū)ο缶幊汤锒际翘貏e基礎(chǔ)的概念,良好的模塊封裝必須做到責(zé)任明確和邊界清晰;每個(gè)類型的對(duì)象有明確的責(zé)任和邊界定義,不同類型的對(duì)象之間通過組合、接口調(diào)用、或者消息機(jī)制完成交互,構(gòu)成易于維護(hù)的系統(tǒng);
但是在React里,這個(gè)設(shè)計(jì)方式變得難以付諸實(shí)施;
React的機(jī)制是父組件不該直接訪問子組件,因?yàn)樽咏M件的生命周期不是父組件維護(hù)的,是React維護(hù)的;React父組件不去訪問子組件也意味著子組件需要的狀態(tài)要提升至父組件維護(hù),父組件更新了這些狀態(tài)之后,通過props向下傳遞;
觸發(fā)狀態(tài)改變的原因可以由子組件發(fā)起(看起來更像封裝),但是需要父組件提供Callback,邏輯處理仍然由父組件完成;這意味著子組件的狀態(tài)和行為,都托管到父組件去了,子組件只負(fù)責(zé)渲染和解釋用戶輸入行為;但這給封裝和重用制造了麻煩,相同的邏輯會(huì)重復(fù)書寫在不同的父組件中;
在StateUp模式中,我們明確給出了p-state的定義和實(shí)現(xiàn),即StateUp組件中的靜態(tài)State類,用于構(gòu)造p-state對(duì)象;
StateUp組件的渲染函數(shù)相當(dāng)于f(p-state, v-state, props);
增加的p-state對(duì)象用于維護(hù)原本要提升至React父組件的狀態(tài),以及行為;換句話說,如果使用p-state對(duì)象,原本由子組件托管到父組件維護(hù)的(屬于子組件維護(hù)責(zé)任的)狀態(tài),及其導(dǎo)致的通過props向下傳遞的數(shù)據(jù),應(yīng)該移動(dòng)到p-state內(nèi)維護(hù),組件直接通過this.props.state訪問;
當(dāng)然這不能消除一個(gè)StateUp組件渲染需要的所有props,由于HTML/DOM的結(jié)構(gòu)設(shè)計(jì),完整渲染組件需要的數(shù)據(jù)注定是它的所有父組件容器向下傳遞的數(shù)據(jù)的總和的一部分(即需要用到的部分)。
P-State的維護(hù)p-state的實(shí)現(xiàn)在StateUp模式中有詳細(xì)介紹,這里不贅述;這里先闡述一下基于p-state和v-state概念,StateUp模式中的生命周期問題如何嚴(yán)格表述,然后闡述StateUp模式的數(shù)學(xué)本質(zhì);
在StateUp模式中,StateUp組件A的p-state不在組件A中維護(hù),它需要提升至父組件B,提升有可能是遞歸的,即在父組件B中被繼續(xù)提升;直到某一個(gè)React組件C,把這個(gè)樹狀層級(jí)的p-state對(duì)象放置在自己的v-state (this.state)中,這意味著StateUp組件A的狀態(tài)生命周期,和組件C的視圖生命周期是一致的;
我們把組件C稱為組件A的p-state ancestor;
組件C在它的任何子組件的p-state發(fā)生變化時(shí),都會(huì)調(diào)用this.setState更新自己的v-state,對(duì)于React而言,這觸發(fā)所有子組件的渲染;但由于immutable的數(shù)據(jù)結(jié)構(gòu)和PureComponent的SCU設(shè)計(jì),render是按需的,僅需要render的子組件會(huì)被render;
p-state的更新路徑在StateUp模式中有一些一眼看上似乎不合理的設(shè)計(jì);
const StateUp = base => class extends base { setSubState(name, nextSubState) { let state = this.props.state || this.state let subState = state[name] let nextSubStateMerged = Object.assign(new subState.constructor(), subState, nextSubState) let nextState = { [name]: nextSubStateMerged } this.props.setState ? this.props.setState(nextState) : this.setState(nextState) } setSubStateBound(name) { let obj = this.setSubStateBoundObj || (this.setSubStateBoundObj = {}) return obj[name] ? obj[name] : (obj[name] = this.setSubState.bind(this, name)) } stateBinding(name) { return { state: this.props.state ? this.props.state[name] : this.state[name], setState: this.setSubStateBound(name) } } }
既然我們明確分清了p-state和v-state,為什么p-state的更新,要象上述代碼一樣走React組件的方法,為什么不是把p-state對(duì)象多帶帶構(gòu)建一個(gè)tree,畢竟它是JavaScript對(duì)象,寫起來并不難;
這個(gè)問題的本質(zhì)涉及到了immutable的tree數(shù)據(jù)結(jié)構(gòu)的一個(gè)常見問題,即你不可能構(gòu)建一個(gè)cyclic數(shù)據(jù)結(jié)構(gòu)是immutable的,至少在JavaScript這種有statement沒有l(wèi)azy evaluation的語言里不可能;
事實(shí)上我為這個(gè)想法寫了代碼,例如在父組件的p-state對(duì)象中這樣寫:
// parent component p-state object static State = class State { constructor() { this.sub1 = new Sub.State() this.sub1.parent = this this.sub1.propName = "sub1" } }
這樣就在子組件的p-state內(nèi)裝載了父組件的p-state的引用;看起來在子組件的p-state上似乎可以設(shè)計(jì)一個(gè)setState方法(不是React Component上的setState),直接調(diào)用父組件的p-state對(duì)象上的setState方法,就可以實(shí)現(xiàn)遞歸更新;
但這是一個(gè)假象;考慮如下A/B/C/D的結(jié)構(gòu):
A -> A" B B" C C" D D
在C更新至C"時(shí),D沒有變化,但是D的父對(duì)象不再是B而是B";
解決這個(gè)問題的辦法,也是通用的immutable tree數(shù)據(jù)結(jié)構(gòu)的雙向引用問題的解法,是所謂的Red-Green Tree (參見參考文獻(xiàn))。
Red Green TreeRed-Green Tree在外部看是一個(gè)Tree,在內(nèi)部分成Red Tree和Green Tree,外部訪問通過Red Tree,Green Tree是內(nèi)部的;
Red Tree的結(jié)構(gòu)和Green Tree一模一樣,它是一個(gè)mutable tree,每個(gè)節(jié)點(diǎn)包含自下至上的引用(parent引用)和向右引用Green Tree上的對(duì)應(yīng)對(duì)象,Green Tree是immutable tree,只有自上至下的引用:
red tree green tree A -> null, A" A" B -> A, B" B" C -> B, C" C" D -> B, D" D" A -> null, A" A" B -> A, B" B" C -> B, C" C" D -> B, D" D"
當(dāng)操作C的時(shí)候,Green Tree的A"/B"/C"都會(huì)發(fā)生變化,同時(shí)Red Tree自上至下更新,它的向上引用不變,但是向右的引用全部刷新成最新的Green Tree對(duì)象;這樣既維護(hù)了雙向引用,又實(shí)現(xiàn)了immutable;
在StateUp模式中,Component相當(dāng)于Red Tree上的節(jié)點(diǎn),p-state對(duì)象是Green Tree上的節(jié)點(diǎn);Component的this.prop.state相當(dāng)于向右引用,render時(shí)自上至下更新(B" -> B");this.prop.setState相當(dāng)于向上引用(B->A),它是穩(wěn)定的,這個(gè)穩(wěn)定引用保證在更新D時(shí)可以先找到父節(jié)點(diǎn)B然后找到最新的B",從而正確實(shí)現(xiàn)D在父對(duì)象里的引用更新;
由于React自上至下渲染,所以在父組件內(nèi)拿子組件的引用是危險(xiǎn)的,因?yàn)榭赡苓^期;但是子組件向父組件的引用在每次渲染之后都是保證正確的;
所以在StateUp模式中,通過用Component承擔(dān)Red Tree的責(zé)任,保證p-state tree可以實(shí)現(xiàn)immutable的Green Tree,有此帶來p-state對(duì)象的高可維護(hù)性和性能保證;
我曾經(jīng)認(rèn)為stateBinding函數(shù)實(shí)現(xiàn)了兩個(gè)prop傳遞是不太合理的設(shè)計(jì),但從上面的圖示看這非常合理,其中state是子組件的向右引用,setState是子組件的向上引用,利用React的props和render機(jī)制實(shí)現(xiàn)Red-Green Tree的更新,這是React和Immutable的完美結(jié)合。
狀態(tài)管理如果去對(duì)比其他的React狀態(tài)管理器,使用這里給出的p-state和v-state概念,會(huì)發(fā)現(xiàn):
大多數(shù)狀態(tài)管理器把p-state提升到最頂層,構(gòu)建外部狀態(tài)樹;
狀態(tài)管理器需要用戶手工代碼來實(shí)現(xiàn)組件更新綁定,以提高效率,但這是理論上的美好,實(shí)際上程序員不會(huì)對(duì)更新做太細(xì)粒度的管理,除非遇到嚴(yán)重性能問題;
各種狀態(tài)管理器都在試圖利用immutable來做性能優(yōu)化,但是沒有觸及問題的本質(zhì),即Red-Green Tree問題,這也是React的本質(zhì);如果你僅僅使用全局狀態(tài)樹,你只做對(duì)了問題的一半。
相信每個(gè)深入思考過React的外部狀態(tài)樹和組件樹關(guān)系的程序員都曾經(jīng)在大腦中有過這樣的問題,它們兩個(gè)到底該不該一致?
StateUp模式給這個(gè)問題一個(gè)明確的回答:應(yīng)該,但不是在React組件層面上的,而是StateUp組件層面的;更確切的說是p-state ancestor組件構(gòu)成的樹,就是Model的結(jié)構(gòu)樹,它包含運(yùn)行時(shí)組件狀態(tài)組合結(jié)構(gòu)和生命周期兩方面的定義;而每個(gè)節(jié)點(diǎn)拓?fù)湔归_的React Component子樹,僅具有視圖層的含義;
所以在設(shè)計(jì)時(shí)仔細(xì)考慮p-state ancestor的處理,是對(duì)狀態(tài)該寫在哪里的最有幫助的思考;同時(shí),基于StateUp模式,這個(gè)Model的結(jié)構(gòu)是自動(dòng)組出來的,不是開發(fā)者獨(dú)立定義的;
React的this.state僅針對(duì)v-state設(shè)計(jì),在沒有p-state對(duì)象封裝的情況下,它相當(dāng)于把p-state ancestor的子樹展開后,內(nèi)部所有形式無態(tài)但本該有態(tài)的組件的態(tài)和行為都提升到該組件內(nèi)實(shí)現(xiàn),為代碼重用和維護(hù)帶來很大麻煩;
從前面Red-Green Tree的分析可以看出,提取p-state進(jìn)行對(duì)象封裝,不但是可行的,而且是恰當(dāng)?shù)模梢杂行Ю肞ureComponent特性提高最高渲染效率,在模型上也有數(shù)學(xué)算法的支撐。
因?yàn)楣ぷ鞣泵ξ覠o意把StateUp模式搞成象redux那樣的流行項(xiàng)目,代碼量也撐不起一個(gè)項(xiàng)目的規(guī)模;而且StateUp的代碼本身也還顯得過于簡(jiǎn)陋,也許讓p-state對(duì)象能夠emit event可以創(chuàng)造更多的便利,等等;
但是在工程實(shí)踐上我會(huì)積極實(shí)踐這種方式,遇到實(shí)際問題也會(huì)盡最大努力去在這個(gè)框架下尋求解決方案,畢竟在目前階段看起來,StateUp模式把UI的開發(fā)帶回了我們熟悉的面向?qū)ο箢I(lǐng)域,對(duì)各種復(fù)雜的行為模式和結(jié)構(gòu)模式,都有大量的成熟模式可用,而不必在非常割裂的組件交互機(jī)制下感覺捉襟見肘。
最后這20行代碼是我一年多的React開發(fā)實(shí)踐中寫過的最好的代碼,它很粗糙,但是它背后的算法模型有異常簡(jiǎn)單強(qiáng)大的力量;
它并不是基于Red-Green Tree推演的結(jié)果,而是偶得后對(duì)其做更深層面的思考,發(fā)現(xiàn)它完全契合了immutable數(shù)據(jù)結(jié)構(gòu)和函數(shù)式編程的設(shè)計(jì)思想;而immutable,是我們目前已知雖然性能并非最佳,但是解決自動(dòng)刷新問題的最簡(jiǎn)單手段;同時(shí)函數(shù)式編程的易于調(diào)試也是巨大的工程收益。
歡迎大家討論和發(fā)表意見。
參考文獻(xiàn)REF 1: https://blogs.msdn.microsoft....
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/81957.html
摘要:一般這種情況會(huì)在類的構(gòu)造函數(shù)內(nèi)創(chuàng)建一個(gè)屬性,引用或詞法域的,但后面會(huì)看到我們有更好的辦法,避免這種手工代碼。 換句話說,StateUp模式把面向?qū)ο蟮脑O(shè)計(jì)方法應(yīng)用到了狀態(tài)對(duì)象的管理上,在遵循React的組件化機(jī)制和基于props實(shí)現(xiàn)組件通訊方式的前提之下做到了這一點(diǎn)。 ---- 少婦白潔 閱讀本文之前,請(qǐng)確定你讀過React的官方文檔中關(guān)于Lifting State Up的論述: ht...
摘要:目的是為了解決在重用的時(shí)候,持久和方法重用的問題。換句話說你不用擔(dān)心把組件寫成模式不好重用,如果你需要傳統(tǒng)的方式使用,一下即可。 這篇文章所述的思想最終進(jìn)化成了一個(gè)簡(jiǎn)單的狀態(tài)管理模式,稱React StateUp Pattern,詳細(xì)介紹請(qǐng)參閱:https://segmentfault.com/a/11... 寫了一個(gè)非常簡(jiǎn)單的實(shí)驗(yàn)性Pattern,暫且稱為PurifiedCompon...
摘要:上例的功能塊定義了如下節(jié)點(diǎn)樹入口節(jié)點(diǎn)是面板,結(jié)合該節(jié)點(diǎn)的函數(shù)書寫特點(diǎn),我們接著介紹最佳實(shí)踐如何處理功能塊之內(nèi)的編程。 本文介紹 React + Shadow Widget 應(yīng)用于通用 GUI 開發(fā)的最佳實(shí)踐,只聚焦于典型場(chǎng)景下最優(yōu)開發(fā)方法。分上、下兩篇講解,上篇概述最佳實(shí)踐,介紹功能塊劃分。 showImg(https://segmentfault.com/img/bVWu3d?w=6...
摘要:的科學(xué)定義是或者,它的標(biāo)志性原語是。能解決一類對(duì)語言的實(shí)現(xiàn)來說特別無力的狀態(tài)機(jī)模型流程即狀態(tài)。容易實(shí)現(xiàn)是需要和的一個(gè)重要原因。 前面寫了一篇,寫的很粗,這篇講講一些細(xì)節(jié)。實(shí)際上Fiber/Coroutine vs Async/Await之爭(zhēng)不是一個(gè)簡(jiǎn)單的continuation如何實(shí)現(xiàn)的問題,而是兩個(gè)完全不同的problem和solution domain。 Event Model 我...
摘要:匿名函數(shù)是我們喜歡的一個(gè)重要原因,也是,它們分別消除了很多代碼細(xì)節(jié)上需要命名變量名或函數(shù)名的需要。這個(gè)匿名函數(shù)內(nèi),有更多的操作,根據(jù)的結(jié)果針對(duì)目錄和文件做了不同處理,而且有遞歸。 能和微博上的 @響馬 (fibjs作者)掰扯這個(gè)問題是我的榮幸。 事情緣起于知乎上的一個(gè)熱貼,諸神都發(fā)表了意見: https://www.zhihu.com/questio... 這一篇不是要說明白什么是as...
閱讀 4693·2021-11-18 13:23
閱讀 896·2021-09-22 15:24
閱讀 1920·2021-09-06 15:00
閱讀 2619·2021-09-03 10:30
閱讀 1278·2021-09-02 15:15
閱讀 2056·2019-08-30 15:54
閱讀 3030·2019-08-30 15:44
閱讀 1449·2019-08-29 15:12