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