摘要:是分發器,是數據與邏輯處理器,會在注冊針對各個命令字的響應回調函數。當按如下方式觸發回調時,回調函數具備事件的特性。
本系列博文從 Shadow Widget 作者的視角,解釋該框架的設計要點。本篇解釋 Shadow Widget 在 MVC、MVVM、Flux 框架之間如何做選擇。
1. React Flux 框架Facebook 官方為 React 提出了 flux 框架,也自己實現了一個 flux.js,盡管這個庫設計得很差勁,但所有第三方為 React 開發的單向數據流方案,起點都是該庫官方所提的 Flux concepts,下面是經典結構圖:
Action 可簡單理解為指令(或命令),由命令字 type 與命令參數 data(或稱 payload)組成。Dispatcher 是分發器,Store 是數據與邏輯處理器,Store 會在 Dispatcher 注冊針對各個命令字的響應回調函數。View 就是 React Component,View 常使用 Store 中的數據并訂閱 Store 發生變化來刷新自身顯示。
幾個部件之間數據單向流動,如下:
Action -> Dispatcher -> Store -> View
形成單向流動的原理較簡單,大致這樣,Store 在 Dispatch 注冊的回調函數,由 Action 觸發,Dispatcher 解析命令字,找出相應回調用函數實現調用即可。當 Dispatcher 按如下方式觸發回調時,回調函數具備事件的特性。
setTimeout( function() { callback(); },0);
如果立即調用 callback,那只是回調,如果延時 0 秒會讓 callback 在下個周期被調用,就成事件了,單向數據流因此得到保證。
當然,上面介紹非常簡略,把核心機制講明白,reflux、redux 讓注冊回調變事件也都用這個機制。當然,事件化回調的處理過程可能很復雜,比如 Dispatcher 還提供 waitFor() 等待一項或多項 Action 的接口,我們略去不細講。
2. React 中的 MVCReact 實現的虛擬 DOM 部分(即核心庫 react.js 與 react-dom.js)是 MVC 中的 "V",其 MVC 框架圖如下:
當你只使用 React 的核心庫,未使用 reflux、redux 等單向數據流機制時,所用的 MVC 就是上圖樣子。如何構造 Controller 與 Model 是自由的,甚至你想將它改造成 MVVM 也是自由的,畢竟 React 的核心庫只提供虛擬 DOM 映射,與 HTML 原生的 DOM 一起提供 "View"。后面我們真的要介紹怎么改成 MVVM。
Flux、MVC、MVVM 這三者是對等的架構,我們不能直接將 Flux 框架往 MVC 上套。
3. 復雜環境對 MVC 框架的影響在 React 中使用 MVC 主要缺陷是:當應用規模變大,M, V, C 之間依賴關系會變復雜。下圖還不算太復雜,只用到 2 個 Module。
React 虛擬 DOM 對真實 DOM 做了一次抽像,附加 props、state 等概念,再加上異步時序干擾,原先還勉強玩得轉的 MVC,已變得很不好用,開發、調試、定位問題都變困難了。
引入 Flux 能有針對性的緩解上述困難。其一,用單數據流向串接各 View,讓與 Model 交互的那個 View(也稱 Controller View)承擔設計復雜性,其它 View 只做簡單工作,如展示界面、簡單響應鼠標點擊等操作。
其二,用 Action 與 Dispatcher 簡化 Controller,不弄那么多 Controller,歸總到一個 Dispatcher。其三,采用 Functional Reactive Programming 方法構造響應式的單向數據流機制,以此應對異步時序問題。
React 生態鏈中有多種 Flux 實現,他們本質一樣,表面差別不算大,通常幾句話就能概括。reflux 采用多 store 方案,把用于集中分發的 Dispatcher 簡化掉了,redux 采用單 store 方案,把分發 Action 后的處理分解給眾多 Reducer 函數,也就是說,上圖多個 Store 的功能,用 "單 Store + 眾多 Reducer 函數" 替換。
4. Shadow Widget 與 Redux 走在兩個方向上Redux 最大優點是實施徹底函數式編程,最大缺點也是徹底函數式。它本身并未簡化設計復雜性,只是轉移復雜性,但按官方原生的 Flux 概念,我們是按對象方式理解一個個 Store 的,在設計時,處理 Store 與 View,以及與 Action 之間關系時,都按對象方式去思考的,現在把復雜性轉移到眾多 reducer 函數上,函數式思維不利于設計分解(相對對象化思維而言)。
Redux 之所以能盛行,與 React 自身限制有關。React 的虛擬 DOM 樹限制數據單向(向下)傳遞,跨節點讀取屬性極不方便,如果我們把所有服務于 render 的 state 數據,獨立到節點之外的全局函數(reducer)中去組裝呢?所有用到的 state 串一起,形成一個大的全局變量(就是單 Store),reducer 函數想怎么讀就怎么讀。這個方案以大幅度函數式改造為代價,來突破 React 的限制。
Shadow Widget 做的正相反,嘗試維持對象化思維習慣,把 Store 與 ViewModel 合一(后面還有詳細說明)以便減輕思考負擔;通過建立 Widget 樹,用 this.componentOf() 快速檢索相關節點,以求方便的存取屬性;再設計 duals 雙源屬性,建立一套能自動識別數據變化,并驅動單向數據流的機制。
5. Controller View 數據傳遞我們研究一下 Controller View 與 Store 對接及與下級 View 的連接關系,取上圖局部,放大講解,如下:
當 Store 中有數據更新,通知 Controller View 更新界面,Controller View 就從 Store 讀得 state 數據,來更新自己的 state。而自身 state 變化將觸發下級 View 聯動更新,變化的信息在各子級借助 props 屬性實現傳遞。
為下文講解作準備,這里我們先拎一拎 Store 該具備的特性:
要提供事件通知功能,當 Store 中的 state 數據有變化,通知 Controller View 刷新界面。
對 Controller View 暴露 state 數據,有兩種設計可選,一是讓事件通知中帶 state 數據,二是事件通知不帶數據,要由 Controller View 主動到 Store 查詢。結合 FRP 編程特點,第二個設計更好,如果數據連續多次更新,從 Store 讀數據應合并為一次,取最新值。
何時通知 Controller View 刷新可能比較復雜,涉及條件組合,比如要 Action A 與 Action B 都發生后,才能觸發事件通知。
6. 向 MVVM 演化我們換一個角度看 flux 框架,傳遞 Action 相當于 "emit
這么弱化、簡化后,Flux 框架就剩 Store 與 View,參照 MVC 框架,這里 Store 與 MVC 中 Model 是對應的,某種程度上說,Flux 概念與 MVC 具備一定兼容性。
reflux 的 Store 仿 React Component 設計 API,學習成本進一步降低,遺憾的是它是多 Store 結構,一個 Store 對應一個 View(有時對應多個),Store 變多后容易讓開發者感到困惑,許多屬性設計一時想不清楚該放在 Store,還是放在 View,經常換來換去。這里我沒說多 Store 設計不對,單 Store 有單 Store 的問題。而是,多 Store 與 多 View 之間如何思考定位有點擰巴,不像 MVVM 那么直接。
MVVM 采用雙向綁定,View 的變動自動反映到 ViewModel,這是非常簡單易用的方式,MVVM 在人性化方面比前端其它框架好出很多,因為設計一項功能,開發者首先想的是界面怎么體現,加個按鈕,還是加個輸入框,然后圍繞著按鈕或輸入框,思考有什么動作,比如,點擊按鈕后下一步做什么。換成 Flux 思考方式,Store 與 View 之間如何交互要多思考一次,還不以 "界面該怎么呈現" 為思考原點,因為 Action 與 Dispatch 的設計促使你先考慮 Store 的數據結構。
如果讓 MVVM 再支持 "所見即所得" 的可視化設計,它的易用性將拉開 Flux 更遠,加上 Flux 天然的函數式編程傾向,疊加 react-router 等工具,也自然以路由指令、Action命令、狀態數據為思考出發點。比如 react-router 強調,以 "路由" 如何設置為功能開發的第一出發點,不像 MVVM 是以交互界面設計為第一出發點。所以,說句實話,React 生態鏈上的工具比 Vue 難用得多,這也是 React 急需 Shadow Widget 之類工具的理由。
現在我們明確了引入 MVVM 的收益,非常值得做。問題關鍵是,它如何與 Flux 共存?
首先,Flux 中的 Store 與 Controller View 可以合并,大膽一點,肯定不會死人。以 reflux 現有設計為例,如果一個 React Component 節點不顯示到界面,比如 節點,或者 comment 注釋節點,或者 style.display="none" 的 其次,由前面總結的 Flux 中 Store 該具備的 3 項特性,與 MVVM 的雙向數據綁定需求高度重合,以 Shadow Widget 已實現的功能舉例: 雙源屬性具有事件通知功能,它可以被偵聽,修改雙源屬性的值可以觸發事件,刷新 trigger 表達式也能觸發事件。 將 Controller View 與 Store 合二為一,state 數據也合二為一,省去了兩者之間同步。 Shadow Widget 的可計算性屬性支持 any, all ,strict 三種條件同步機制,與 reflux 提供的條件組合等效。比如要求 Action A 與 Action B 都發生后,才觸發事件,腳本表達式用 "all:" 前綴指示即可。 當然,這些 Flux 中 Store 的需求是附加在 React Component 之上的,如果 Component 想顯示界面(而不是用作純 Store,把界面隱藏起來),盡管顯示好了,無非這樣的節點還同時具備 Store 的功能。 改造后 Shadow Widget 的 MVVM 如下圖: 其中,雙合一 "Store + Controller View" 是 "VM" 或 "VM + V",視該 React Component 需不需在界面顯示而定,若同時還用作界面元素的就是 "VM + V"。 Flux 要求的 Action 與 Dispatcher 已被各節點的 duals.attr 屬性代替,其中屬性名(attr) 與 Action 的命令字(type)對等,屬性值與 Action 的數據(Payload)對等。各個 duals.attr 可被自身節點或其它節點偵聽,當 duals.attr 取值變化時,相應的偵聽函數會按事件方式自動被回調。 至于 Model,它最簡的形態就是各 View 節點的 duals.xxx 屬性。遇到復雜的,不妨定義專職的數據服務,用不顯示界面的 Controller View 來定義,如上所述,這是 "VM"。但當它只處理 duals.attr 數據,沒有其它功能時,"VM" 的角色將退化為 "M"。比如 ajax 數據服務(用于從服務側請求數據,往服務側保存數據),完全可以用 style.display="none" 的 值得一提的是:Shadow Widget 的 MVVM 與 Flux 框架是兼容的,與 Functional Reactive Programming 編程也是兼容的。上圖按 Flux 方式繪圖,若要體現 MVVM,這么繪制: 上圖中,區分 View 與 ViewModel 的主要依據是:一個 Component 節點是否納入編程,若納入編程(定義投影定義,或 idSetter 函數)應視作 ViewModel,否則應視作 View,即使這個 View 使用一些 trigger, $for, $if 等控制指令也如此。 一個 Vue 的 MVVM 例子如下。 對應于 Shadow Widget,界面 View 定義如下: VM 定義如下: Shadow Widget 的 MVVM 與 Vue 相比,更突出從 "界面布局" 出發思考設計,更傾向于函數式編程風格。比如: 在一個 VM 中,Shadow Widget 將 Model 分散在各層多個 React Componet 中,數據服務以 duals.xxx 方式提供。而 Vue 集中在一處定義數據。 數據分散,處理函數也分散定義,所以上面 Shadow Widget 的事件函數,要用 this.componentOf() 動態查找相關節點。Vue 集中定義數據,與數據相關的節點、動作、事件等函數也隨之被鎖定。這兩種方式各有利弊,Vue 方式簡單明了,Shadow Widget 更動態、更函數式,使用要復雜些,但應付各種變化自由些,功能更強些,比如各層節點動態增刪、改換。 Shadow Widget 要支持界面可視化設計,可視設計的產出是界面元素的疊加物,當這種疊加物含有函數定義時,保存設計成果,或緩存設計結果(用于 undo 與 redo)將很成問題。因為函數定義要附帶上下文才有意義,另外,函數定義體(即 JS 腳本)可以是任意字符,混在界面定義中,給結構化的解析設計結果也帶來挑戰。所以,Shadow Widget 限制可視設計過程中使用函數化數據,設計態的 props 數據傳遞不能有函數對象。 在 Shadow Widget 中,與 JSX 對等的界面數據化描述格式叫 json-x,因為 JSON 數據不能帶 function 定義,在數據的序列化方面與 JSON 接近,所以就叫 json-x 格式了。界面的可視化設計過程中,輸出的(或緩存的),就是這個基于 json-x 的數據。 Shadow Widget 借助在 main[widget_path] 預登記投影類定義,實現 function 的動態捆綁,還借助 idSetter[id_string] 預登記 idSetter 函數,這兩者讓界面可視化設計時避開了函數對象的傳遞,設計態下投影定義與 idSetter 函數不被捆綁。 不過在設計態,某些第三方庫需要讓特定構件捆綁函數對象,比如封裝 slides.js 形成直方圖、餅圖等樣板,在可視化設計中,捆綁的函數就要啟用,否則可視化交互設計中直方圖、餅圖等不被繪制。 Shadow Widget 為這類需求提供兩種解決方案。其一,使用 初始化列表(注意,不是 W.$onLoad),該列表中的初始化函數在設計態也被調用,通常用它注冊特定廠商的庫化 UI 節點。庫化 UI 供設計中引用,它自身不介入中間設計成果的保存或緩存,在 $$onLoad 初始化函數中可捆綁投影類,或傳遞 idSetter 函數。 其二,類似 T.rewgt.DelayTimer 注冊一個自行開發的 WTC 類,然后界面的轉義標簽就可以用 我一直認為,開發語言、編程框架只是人類思維的輔助表達器,人腦觀照世界,見山是山,見水是水,人要一個個去認,事物要一件件識別,探究復雜的事物,都是分層拆解的思路。具體到前端開發,客戶需求高頻變化,在并不純粹的瀏覽器方框之中,過分強調純粹的函數式編程肯定要誤人子弟。 見過 React 家族的太多開發者,太多工具陷在追求 "純正" 的泥淖里,無法自拔,阿彌陀佛!但愿我的觀點是正確的。 ? 本文參考資料: 阮一峰:MVC,MVP 和 MVVM 的圖示 facebook/flux:Flux Concepts fluxxor.com:What is Flux? Andrew Ray:The ReactJS Controller View Pattern 本專欄歷史文章: 介紹一項讓 React 可以與 Vue 抗衡的技術 React 可視化開發工具 Shadow Widget 非正經入門(之一:React 三宗罪) React 可視化開發工具 Shadow Widget 非正經入門(之二:分離界面設計) React 可視化開發工具 Shadow Widget 非正經入門(之三:雙源屬性與數據驅動) 文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。 轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83453.html 摘要:本篇解釋中類的控制指令,與指令式界面設計相關。本專欄歷史文章介紹一項讓可以與抗衡的技術可視化開發工具非正經入門之一三宗罪可視化開發工具非正經入門之二分離界面設計可視化開發工具非正經入門之三雙源屬性與數據驅動可視化開發工具非正經入門之四
本系列博文從 Shadow Widget 作者的視角,解釋該框架的設計要點。本篇解釋 Shadow Widget 中類 Vue 的控制指令,與指令式界面... 摘要:前言非正經入門是相對正經入門而言的。不過不要緊,正式學習仍需回到正經入門的方式。快速入門建議先學會用拼文寫文檔注冊一個賬號,把庫到自己名下,然后用這個庫寫自己的博客,參見這份介紹。會用拼文寫文章,相當于開發已入門三分之一了。
本系列博文從 Shadow Widget 作者的視角,解釋該框架的設計要點,既作為用戶手冊的補充,也從更本質角度幫助大家理解 Shadow Widget 為什么這... 閱讀 1111·2021-09-22 15:37 閱讀 1135·2021-09-13 10:27 閱讀 2473·2021-08-25 09:38 閱讀 2449·2019-08-26 11:42 閱讀 1532·2019-08-26 11:39 閱讀 1561·2019-08-26 10:58 閱讀 2325·2019-08-26 10:56 閱讀 2573·2019-08-23 18:08idSetter["btn_todo"] = function(value,oldValue) {
if (value <= 2) {
if (value == 1) { // init process
this.setEvent( {
$onClick: function(event) {
var inputComp = this.componentOf("http://input");
var text = inputComp.duals.value.trim();
if (text) {
var dataComp = this.componentOf(0);
dataComp.duals.data = ex.update(dataComp.duals.data, {
$push:[{text:text}],
});
inputComp.duals.value = "";
}
},
});
}
return;
}
};
Shadow Widget 先考慮界面如何設計,確定界面元素后,再考慮相關數據綁捆到哪個節點更方便,所以數據服務是分散的,Vue 則提前考慮數據結構如何設計,要集中。所以,這個例子中,用 Vue 時 data.newTodo 定義到 Model,用 Shadow Widget 則視作一個過程數據,不必在對外接口體現。相關文章
React 可視化開發工具 Shadow Widget 非正經入門(之五:指令式界面設計)
React 可視化開發工具 Shadow Widget 非正經入門(之一:React 三宗罪)
發表評論
0條評論
msup
男|高級講師
TA的文章
閱讀更多
如何在godaddy購買主機-godaddy主機怎么樣?
OpenInfra 基金會已與微軟簽署協議,成為最新的白金會員
Aperture:$10.3/月/1GB內存/8GB空間/600GB流量/150Mbps-500Mb
JS 中 __proto__ 和 prototype 存在的意義是什么?
現代CSS進化史
JavaScript數據結構與算法(十)自平衡樹
vue-video-player使用筆記(兼容m3u8)
結合作用域,執行上下文圖解閉包