成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

重磅:前端 MVVM 與 FRP 的升階實(shí)踐 —— ReRest 可視化編程

Cciradih / 3318人閱讀

摘要:是前端開發(fā)領(lǐng)域新興的方法論體系,它繼承了與編程理念,在技術(shù)上有不少創(chuàng)新。但專利與開源協(xié)議是平行的兩個(gè)世界,改底層也不大容易解決問題。此外,要求在中結(jié)合各屬性的是否變化,判斷是否該觸發(fā)更新。

ReRest (Reactive Resource State Transfer) 是前端開發(fā)領(lǐng)域新興的方法論體系,它繼承了 MVVM 與 FRP 編程理念,在技術(shù)上有不少創(chuàng)新。本文從專利稿修改而來,主要介紹 ReRest 原理與若干實(shí)踐經(jīng)驗(yàn)。

?

說明:文章作者授權(quán)任何組織或個(gè)人,在不更改原文內(nèi)容(包括本段)的前提下,可以自由轉(zhuǎn)載本文。點(diǎn)擊下載本文 PDF 格式

?

1. 前言

前陣子 React 附加專利條件的開源協(xié)議鬧得沸沸揚(yáng)揚(yáng),國內(nèi)外有多家大公司開始棄用 React,我們也深感困惑,是否該將 shadow-widget 全盤改寫,很猶豫。讓底層脫離 React。但專利與開源協(xié)議是平行的兩個(gè)世界,改底層也不大容易解決問題。Facebook 擁有虛擬 DOM 方面的專利,preact、vue 都可能涉嫌侵權(quán),通過修改底層代碼來規(guī)避還是挺難的。

后來我們決定自己申請(qǐng)專利,以便今后萬一用到,手頭有個(gè)專利可為 shadow-widget 增加話語權(quán)。當(dāng)權(quán)利要求書完稿時(shí),F(xiàn)acebook 宣布 React 回歸真正的 MIT 開源協(xié)議了,真是大喜訊!我們不必?fù)?dān)心專利的風(fēng)險(xiǎn)了,為自家申請(qǐng)專利不再必要 —— 我們創(chuàng)建 shadow-widget 技術(shù)平臺(tái),但無意借此盈利,源碼開放出來讓大家都受益。(PS:不必感謝,如果覺得這項(xiàng)目對(duì)您有用,上 github 為我們加星吧)

本文從專利申請(qǐng)稿改寫而來,內(nèi)容有壓縮,要不文章太長了,另外還增加了可視化編程實(shí)踐相關(guān)的若干內(nèi)容。公布此文還有一個(gè)目的,防止他人偷偷拿我們的技術(shù)申請(qǐng)專利,如果以后真發(fā)現(xiàn)有人這么干了,本文是憑證,大家可以提請(qǐng)專利無效,把別人的保護(hù)條款廢掉。

說明:本文完稿時(shí),Shadow Widget 最新版本為 v1.1.2,產(chǎn)品用戶手冊(cè)對(duì)技術(shù)實(shí)現(xiàn)有更詳細(xì)介紹。

2. 背景

近些年 Web 前端技術(shù)發(fā)展,可以說是框架橫飛的時(shí)代,雖然十年前網(wǎng)頁還正常能打開,IE 還是那個(gè)頑固的 IE,但前端開發(fā)卻已經(jīng)歷翻天覆地的變化。近來比較搶眼的是 React 框架,F(xiàn)acebook 開創(chuàng)性的實(shí)踐了兩種技術(shù):虛擬 DOM 與 Functional Reactive Programming(FRP,函數(shù)式響應(yīng)型編程),這兩種技術(shù)幾乎已成現(xiàn)代前端框架的標(biāo)準(zhǔn)配置。

Facebook 在虛擬 DOM 上原創(chuàng)較多,鉆研很深入,這項(xiàng)技術(shù)也可以說很成熟了。FRP 在 React 的實(shí)現(xiàn)就是那個(gè) FLUX 框架,它不是 Facebook 首創(chuàng),在 React 中用起來也有點(diǎn)磕磕碰碰,尤其在調(diào)和指令式風(fēng)格與函數(shù)式風(fēng)格方面,并不順暢。

另外,盡管十年來 Web 開發(fā)技術(shù)發(fā)展很快,但在可視化開發(fā)方面仍然進(jìn)展緩慢,所有主流框架都在界面的形式化描述上做文章,Angular 與 Vue 擴(kuò)展了標(biāo)簽屬性,增加不少控制指令,React 則全盤引入 JSX 描述方式,他們無一例外的都要求大家,一行行寫腳本去定義界面,而不是 20 年前在 Delphi 與 VB 就已出現(xiàn)的可視化、所見即所得的開發(fā)方式。

本文所提的 ReRest 編程方法,是適應(yīng) Web 可視化開發(fā)要求,融合虛擬 DOM 與 FRP 技術(shù),并克服它們應(yīng)用于主流框架的若干不足,而提出的通用型解決方案。ReRest 方法在 shadow-widget 平臺(tái)有一些實(shí)踐,已取得良好效果。

3. ReRest 要點(diǎn)

ReRest 全稱為 REactive REsource State Transfer,譯為 “響應(yīng)式資源狀態(tài)遷移”,與本概念相關(guān)的提法還有:

ReRest framework,ReRest 框架

ReRest based programming,基于 ReRest 的編程

ReRest-ful design,ReRest 風(fēng)格設(shè)計(jì)

光從字面上看,“響應(yīng)式資源狀態(tài)遷移” 不大好理解,就像縮寫為 REST 的 “Representational State Transfer”,表現(xiàn)層狀態(tài)轉(zhuǎn)移,只看文字,是不大容易搞清楚講的是啥。

ReRest 提倡以 “資源” 的觀點(diǎn)展開設(shè)計(jì),將針對(duì)資源的操作規(guī)格化,統(tǒng)一抽象成 4 類操作,在程序開發(fā)過程中,可視界面的功能塊分解設(shè)計(jì)是一個(gè)維度,基于資源狀態(tài)變遷所帶來的單向數(shù)據(jù)流,構(gòu)成另一個(gè)維度,兩個(gè)維度共同形成一個(gè)正交矩陣,這種開發(fā)方式有效平衡了指令式與函數(shù)式兩種設(shè)計(jì)風(fēng)格,集兩者優(yōu)勢于一身。

ReRest 理念與 REST 有某種相似性。REST 核心含義是用 URL 定位資源,用 HTTP 動(dòng)詞描述操作,它要求服務(wù)側(cè)提供的 RESTful API 中,只使用名詞來指定資源,原則上不使用動(dòng)詞,“資源” 概念可以說是 REST 架構(gòu)的處理核心,針對(duì)資源的操作有 GET, POST, PUT, DELETE 等 HTTP 動(dòng)詞。在 ReRest 框架中,界面可視控件的屬性數(shù)據(jù)視作資源,依據(jù) shadow-widget 實(shí)踐,“資源(Resource)” 則指 React Component 的屬性數(shù)據(jù)。

理解基于 ReRest 的編程,須把握兩個(gè)重點(diǎn):Component 管界面呈現(xiàn),Resource 管數(shù)據(jù)流。前者適用靜態(tài)思維,更偏指令式風(fēng)格,后者適用動(dòng)態(tài)思維,更偏函數(shù)式風(fēng)格。

4. 兩種思維模式

主流的前端框架一直并存靜態(tài)與動(dòng)態(tài)兩種思維模式,舉例來說,Vue 與 Angular 更多采用靜態(tài)思維模式,界面是可描述的,React 更多的用動(dòng)態(tài)思維,界面是可編程的,JSX 看上去也是一種表述形式,但它本質(zhì)是一段 javascript 代碼,你很難將它 “去編程化” —— 把 JSX 從上下文環(huán)境摳出來獨(dú)立使用,事情將變得毫無意義。

我們不必爭論這兩種模式孰優(yōu)孰劣,兩者都有顯著優(yōu)點(diǎn)。F.S.菲茨杰拉德曾說:檢驗(yàn)一流智力的標(biāo)準(zhǔn),就是頭腦中能同時(shí)存在兩種相反的想法,但仍保持行動(dòng)能力。

況且,在前端開發(fā)中,該采用靜態(tài)思維或動(dòng)態(tài)思維的條件還算清晰。比如開發(fā)一個(gè)網(wǎng)頁,大塊功能的界面設(shè)計(jì)應(yīng)采用靜態(tài)思維,比方,在頂部放一個(gè)工具條,左側(cè)放導(dǎo)航,中間放內(nèi)容;簡單界面設(shè)計(jì)應(yīng)以靜態(tài)思維為主,因?yàn)榻缑娼M件很少動(dòng)態(tài)替換;而應(yīng)對(duì)復(fù)雜功能,應(yīng)以動(dòng)態(tài)思維為主,既然 JS 代碼可以控制一切,局部界面用 JSX 定義會(huì)很爽。越是動(dòng)態(tài)變化的界面,應(yīng)該越傾向于用動(dòng)態(tài)的、編程性思維。

Angule 靜態(tài)思維過重,React 動(dòng)態(tài)思維過重,都不好,Vue 從靜態(tài)走向動(dòng)態(tài),易用且適應(yīng)復(fù)雜變化,應(yīng)該說它正前進(jìn)在正確道路上,只是,Vue 兼容兩種風(fēng)格并非一開始就統(tǒng)籌規(guī)劃了,工具復(fù)雜性不容易降下來。

5. 從 FRP 到 FLUX,再到 ReRest

ReRest 在前人已有經(jīng)驗(yàn)基礎(chǔ)上,提出更優(yōu)方法,然后驗(yàn)證,結(jié)合實(shí)踐再調(diào)整、優(yōu)化,React 生態(tài)鏈上系列工具的實(shí)踐是其中最重要的經(jīng)驗(yàn)基礎(chǔ)。

如果只把 React 看作虛擬 DOM 庫,它無疑是一項(xiàng)偉大的發(fā)明,作為 DOM 節(jié)點(diǎn)對(duì)應(yīng)物,可按任意方式使用它。你完全可以在 React 基礎(chǔ)上擴(kuò)展出像 Vue 那樣的指令式描述系統(tǒng),甚到回退到 jQuery 方式也行(偷偷告訴你一個(gè)關(guān)鍵點(diǎn),用 node.__reactInternalInstance$XXX 能反查 React Component),用 React 搭建 MVVM 也完全可能,React 團(tuán)隊(duì)在 SoC(關(guān)注度分離)方面分寸把握得很好。

React 工具鏈普遍遵從濃重的函數(shù)式編程風(fēng)格,從函數(shù)式拓展命令式較為容易,但反過來就困難得多。就像許多編程語言,都從 LISP 普系吸收營養(yǎng),相對(duì)來說,函數(shù)式編程更反映事物的本原,從此出發(fā)更容易理順具有復(fù)雜關(guān)系的框架系統(tǒng)。

由于上面原因,ReRest 的實(shí)踐性探索從 React 開始,而不是 Vue 或其它工具。

5.1 理解 React 的 FRP 機(jī)制

FRP 是響應(yīng)式編程一種范式,由不斷變化的數(shù)據(jù)驅(qū)動(dòng)界面持續(xù)更新,界面更新中,或用戶操作(如鼠標(biāo)點(diǎn)擊)中又產(chǎn)生新的數(shù)據(jù)流,再驅(qū)動(dòng)界面更新,如此循環(huán)往復(fù)。觸發(fā)界面更新的數(shù)據(jù)流也稱事件流,因?yàn)樗男袨榉绞接幸恍┫薅?,不是常?guī)數(shù)據(jù)流動(dòng),它至少要求單流向、細(xì)粒度、按 tick 觸發(fā)。

我們不妨把網(wǎng)頁界面的更新過程,理解成眾多 “驅(qū)動(dòng)更新的時(shí)間片” 的集合,一個(gè)時(shí)間片稱為一個(gè) tick,各 tick 可能前后緊挨著,但兩個(gè) tick 之間至少都有 “調(diào)度間隙”。就像下面 process2 函數(shù)緊隨 process1 執(zhí)行,用 setTimeout(process2,0) 延時(shí) 0 秒,這兩函數(shù)之間就產(chǎn)生 “調(diào)度間隙” 了。

function process1() {
  console.log("in process1");

  setTimeout( function process2() {
    console.log("in process2");
  },0);
}

數(shù)據(jù)變化導(dǎo)致界面更新(即 React 的 render() 調(diào)用),界面更新又觸發(fā)數(shù)據(jù)變化,如果沒有調(diào)度間隙,系統(tǒng)可能陷入無限遞歸,遞歸結(jié)果必然爆棧。React 的 FLUX 框架首先要讓數(shù)據(jù)單向流動(dòng),只要有 “調(diào)度間隙” 區(qū)隔,即使數(shù)據(jù)變化與界面更新無限制的互為觸發(fā),都算單向流動(dòng)。

React 以兩種機(jī)制保障數(shù)據(jù)單向流動(dòng),一是讓 props 只讀,二是 setState() 延后一個(gè)調(diào)度間隙執(zhí)行。后者好理解,前者 “props 只讀” 是間接生效的,因?yàn)?props 與 state 同時(shí)決定 Component 界面如何表現(xiàn),但更改 props 屬性只能在父節(jié)點(diǎn)的 render() 函數(shù)中進(jìn)行,你得用 ownerComp.setState() 觸發(fā)父節(jié)點(diǎn)再次 render(),所以,不管你怎么用,都會(huì)插入 “調(diào)度間隙” 的。

此外,React 要求在 shouldComponentUpdate() 中結(jié)合各屬性的 immutable 是否變化,判斷是否該觸發(fā) render 更新??傊?,上述機(jī)制支持了 FRP 編程以下要求:按時(shí)間切片驅(qū)動(dòng)界面更新,各切片保持細(xì)粒度,讓每次更新最小化、無關(guān)聯(lián)。

5.2 改造雙源驅(qū)動(dòng)

由父節(jié)點(diǎn)決定如何更新的 props.attr,與節(jié)點(diǎn)自己就能決定的 state.attr,兩者共同定義 Component 的界面表現(xiàn),所以 props 與 state 合稱為 “雙源”,只是原生 React 是 “隱式雙源”,ReRest 框架要把它改造成 “顯式雙源”。

實(shí)現(xiàn)原理大致如下:

引入一個(gè)與 props.attrstate.attr 對(duì)等的集合:duals.attr
該集合中的 attrprops.attr 自動(dòng)記錄到 state.attr,通過 duals.attr 讀寫接口,可等效實(shí)現(xiàn)對(duì)相應(yīng) state.attr 的存取,即:讀 duals.attr 等效于讀 state.attr,寫操作 duals.attr = value 等效于執(zhí)行 this.setState({attr:value})。

提供 this.defineDual() 讓用戶手工注冊(cè) duals 屬性
系統(tǒng)還將傳給標(biāo)簽內(nèi)置屬性(如 name,href,src 等)自動(dòng)注冊(cè)為 duals 屬性,此舉方便了編程,否則大量屬性手工編碼去注冊(cè)很麻煩。

defineDual() 實(shí)現(xiàn) setter 回調(diào)的捆綁
比如調(diào)用 this.defineDual("a",setter) 注冊(cè)后,對(duì)它賦值 this.duals.a = value,將自動(dòng)觸發(fā) setter(value,oldValue) 回調(diào)。

經(jīng)上述改造,更改 Component 自身的 props 就不必繞轉(zhuǎn)到父節(jié)點(diǎn)去做了,比如,用類似comp.duals.name = "new_name" 語句直接賦值就好。

這么變動(dòng)將帶來一個(gè)重大影響:上層 FLUX 機(jī)制可以捊直了做。如何實(shí)現(xiàn) FLUX,官方給出了框架建議,React 說我只管虛擬 DOM,如何搭 FLUX 是上層的事,Redux 說,我來管這事,增加 action,增加 reducer,增加 store,不過異步的事你自己解決。什么是 action 呢?就是事件化數(shù)據(jù),什么是 reducer 呢?就是事件處理函數(shù),什么是 store 呢?那個(gè) Component 限制了數(shù)據(jù)讀寫,還搞不清關(guān)聯(lián)子節(jié)點(diǎn)、父節(jié)點(diǎn)在哪,自個(gè)弄一數(shù)據(jù)集就是 stroe。結(jié)果,Redux 繞了很大一個(gè)彎,說把事情解決了,但用戶仍報(bào)怨寫異步很難受呀,這么繞的東西不難受就鬼了!

ReRest 的對(duì)策很簡單,最直接。事件化數(shù)據(jù)就是可偵聽的 duals 屬性嘛,事件處理函數(shù)就是 duals 的 setter 回調(diào),理不清父子從屬關(guān)系,就弄一個(gè) W 樹吧,把各節(jié)點(diǎn)串起來,用 this.componentOf() 按相對(duì)路徑(或絕對(duì)路徑)直接找,至于 store,哪有必要,Component 自身就是 store 嘛!

5.3 資源化

ReRest 嘗試讓 Web 開發(fā)回歸事物本原,網(wǎng)頁開發(fā)主要處理兩樣?xùn)|西:開發(fā)界面、與服務(wù)器交換數(shù)據(jù),它與 Delphi、Qt 等 GUI 開發(fā)工具不該有太大差別,為什么 React 就不能支持 MVVM 呢?MVC 難以適應(yīng)標(biāo)簽化的界面表達(dá)形式,但用 MVVM 是沒問題的。

常規(guī)所見即所得開發(fā)工具,界面設(shè)計(jì)的主體過程是:拖入一個(gè)樣板創(chuàng)建界面組件,選中它對(duì)修改某些屬性,再拖入樣板創(chuàng)建其它組件,設(shè)屬性,重復(fù)操作直至組裝出復(fù)雜界面。外觀設(shè)計(jì)差不多就這些,剩下工作主要是功能實(shí)現(xiàn),實(shí)現(xiàn)類似如何接收鍵盤輸入,如何響應(yīng)按鈕點(diǎn)擊等函數(shù)定義。

原生 React 之所以離常規(guī)可視化設(shè)計(jì)很遠(yuǎn),主要是 Component 屬性成員級(jí)別的設(shè)計(jì)還不夠好,少一層可靜態(tài)依賴的錨點(diǎn),過早套上高度動(dòng)態(tài)變遷的事件流了,所有東西都動(dòng)態(tài)變化,可視設(shè)計(jì)是無法支持的。在 ReRest 設(shè)計(jì)理念中,凡 Component 屬性中公開供控制,或供配置的,都應(yīng)視作 “資源”,“資源” 是靜態(tài)化的概念,就像 RESTful 要求 URL 要用名詞表達(dá)資源,動(dòng)作統(tǒng)一由 HTTP 的 GET, POST, PUT 等表達(dá)一樣,將 Component 屬性 “資源化”,才是問題解決之道。

就 shadow-widget 已有實(shí)踐而言,ReRest 所謂的資源,專指 Component 的靜態(tài)屬性(即 comp.props.attr)與雙源屬性(即 comp.duals.attr。

React 對(duì) Component 渲染組裝在 render 函數(shù)中完成,組裝過程是一段 JS 代碼,因?yàn)?JS 代碼可以任意書寫,如何組裝會(huì)非常靈活。而靈活是一把雙刃劍,功能雖然強(qiáng)大了,但缺少穩(wěn)定形態(tài),對(duì)建立 MVVM 框架與可視化開發(fā)都不利。

ReRest 希望將渲染過程,改造成開發(fā)主體依賴于對(duì) “資源” 的操作,當(dāng)然,這里的 “資源” 是動(dòng)作化了的,也就是,讀寫資源會(huì)自動(dòng)觸發(fā)預(yù)設(shè)的關(guān)聯(lián)動(dòng)作。換一句話來說,ReRest 想把 render 函數(shù)改造成一種固定格式,不必再通過寫一段過程代碼實(shí)施控制,而改成對(duì)若干 duals.attr 讀寫,以此驅(qū)動(dòng)渲染過程的定制處理。

ReRest 對(duì)渲染的 “資源化” 改造過程,本質(zhì)是將過程控制邏輯,挪到 “資源” 附屬的動(dòng)作函數(shù)中書寫。

5.4 渲染臨界區(qū)

如下示例:

01 render() {
02   // 進(jìn)入渲染臨界區(qū)
03   渲染臨界區(qū)的過程處理 ...
04   // 退出渲染臨界區(qū)
05 
06   固定程式的其它 render 處理 ...
07 }

“渲染臨界區(qū)” (Rendering Critical Section) 中的代碼(上面 03 行)用來驅(qū)動(dòng)本 Component 各個(gè) duals.attr 附屬動(dòng)作。上面 06 行,讓附屬動(dòng)作處理后的結(jié)果生效,完成渲染輸出。經(jīng)此改造,用戶不必再定義各 Component 的 render() 函數(shù)。

在 “渲染臨界區(qū)” 執(zhí)行的代碼有特別要求,其一,render 函數(shù)因?yàn)橛?React 內(nèi)核發(fā)起,使用有一些限制,比如 render 過程中再次觸發(fā)更新、用 ReactDOM.findDOMNode 查找 node 節(jié)點(diǎn)等,不過,隨著 React 版本優(yōu)化,這些限制逐漸變少(比如目前版本在 render 函數(shù)中調(diào)用 findDOMNode 不再報(bào)錯(cuò)了)。其二,ReRest 資源的行為函數(shù)如何被調(diào)用,在臨界區(qū)中與臨界區(qū)外有差別,下文馬上介紹。

5.5 資源的行為定義

ReRest 區(qū)分兩類資源,只讀資源(即 comp.props.attr)與可寫資源(即 comp.duals.attr),對(duì)于前者,在 Component 生存周期內(nèi),只支持 “讀” 行為,而對(duì)于后者,支持讀、寫、setter 處理、listen 處理共 4 種行為。

這 4 種行為含義如下:


props.attr 直接讀取,或從 duals.attr 讀取由系統(tǒng)返回 state.attr 的值。


對(duì) duals.attr 賦值,系統(tǒng)除了把值賦給 state.attr 外,還觸發(fā)相應(yīng)的 “setter處理” 與 “l(fā)isten處理”。

setter 處理
這個(gè) setter 就是 defineDual(attr,setter)setter 回調(diào)函數(shù)。對(duì)于同一 Component 的同一 attr,可以調(diào)用多次 defineDual() 注冊(cè)多個(gè)回調(diào)函數(shù),給 attr 賦值后,各回調(diào)函數(shù)依次被調(diào),調(diào)用順序與注冊(cè)順序相同。

listen 處理
對(duì)一個(gè)已存在 comp.duals.attr,可調(diào)用 comp.listen(attr,fn) 登記一項(xiàng)偵聽,當(dāng) attr 值發(fā)生變化后,系統(tǒng)會(huì)自動(dòng)調(diào)用 fn(value,oldValue)。同一 comp.duals.attr 支持在多處偵聽,我們可以為兩個(gè)(或多個(gè)) duals.attr 建立偵聽關(guān)聯(lián),一處更新,其它地方也聯(lián)動(dòng)更新。

setter 處理與 listen 處理的適用場合有明顯差別,setter 函數(shù)只在渲染臨界區(qū)的處理過程中被調(diào)用,listen 函數(shù)在觸發(fā)后(即更改 duals 屬性值)必定延后一個(gè) “調(diào)度間隙” 才被執(zhí)行,所以它必然不在任何節(jié)點(diǎn)的渲染臨界區(qū)內(nèi)執(zhí)行。

在同一節(jié)點(diǎn)的渲染臨界區(qū)內(nèi),setter 函數(shù)可被連續(xù)調(diào)用,當(dāng)前節(jié)點(diǎn)中不同 duals.attr 的 setter,或同一 attr 的 setter 可串連執(zhí)行,這意味著,臨界區(qū)內(nèi)對(duì)當(dāng)前節(jié)點(diǎn) duals.attr 賦值可能會(huì)引發(fā)遞歸重入,各次 setter 調(diào)用之間沒有 “調(diào)度間隙” 區(qū)隔。比如對(duì) comp1.duals.attr1 修改,導(dǎo)致 comp1.duals.attr2comp2.duals.attr3 修改,而 comp1.duals.attr2 修改可能再導(dǎo)致 comp1.duals.attr1 修改,這時(shí)對(duì) comp1.duals.attr1 賦值可能導(dǎo)致該屬性的 setter 函數(shù)遞歸調(diào)用,而引發(fā)的 comp2.duals.attr3 更改卻是延后一個(gè) “調(diào)度間隙” 的,因?yàn)?comp2 的雙源屬性 setter 函數(shù)將在 comp2 的臨界區(qū)被調(diào)用。

setter 與 listen 處理反映了兩類資源聯(lián)動(dòng)的需求,常規(guī)情況下,隔一個(gè) “調(diào)度間隙” 可確保數(shù)據(jù)單向流動(dòng),而特殊情況下,對(duì)于緊密相關(guān)的資源聯(lián)動(dòng),如果總有 “調(diào)度間隙” 隔著,顯然會(huì)影響運(yùn)行效率,上述機(jī)制保留了重入式 setter 回調(diào)是有意義的。

6. 范式變換

Redux 是 React 生態(tài)鏈中提供 FLUX 框架的一個(gè)典型工具,有代表性,接下來介紹范式變換與它有關(guān)。

Redux 以 “Action” 的觀點(diǎn)展開設(shè)計(jì)(其它 FLUX 工具也大都如此),ReRest 則要求以 “Resource” 的觀點(diǎn)展開設(shè)計(jì),Action 是動(dòng)態(tài)的動(dòng)作,Resource 是靜態(tài)的資源,兩者差別可用 “非 RESTful” 風(fēng)格與“RESTful” 的差別來類比?;谶@兩種觀點(diǎn)的設(shè)計(jì)存在范式變換關(guān)系,下面我們用 Redux 與 shadow-widget 的 FLUX 實(shí)現(xiàn)差異為例,展開說明。

6.1 單 Store 變多 Store

拿 Redux 用戶手冊(cè)提到的 Todo 例子來說,增加一條 todo 記錄,基于 Action 觀點(diǎn)會(huì)先設(shè)計(jì)一個(gè) Action 定義:

const ADD_TODO = "ADD_TODO";

var actTodo = {
  type: ADD_TODO,
  text: "Build my first Redux app"
};

然后,設(shè)計(jì)一個(gè) reducer 響應(yīng)這個(gè) Action:

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [ ...state, {
        text: action.text,
        completed: false,
      }];
    // ...
  }
  // ...
}

Redux 采用單一的大 Store 結(jié)構(gòu),ReRest 要求的資源卻是小數(shù)據(jù),相當(dāng)于把 Redux 的大 Store 分割成許多小塊,一個(gè)小塊就是一個(gè)資源。針對(duì) todo 列表,資源項(xiàng)用 duals.todoList 表示,指定它的初值是空數(shù)組。

this.defineDual("todoList",null,[]);

然后如下代碼添加一條 todo 記錄,就對(duì)等實(shí)現(xiàn)了上述 reducer 功能:

utils.update(this,"todoList", {$push: [{
  text: "Build my first Redux app",
  completed: false,
}]});

ReRest 的 Store 具備兩個(gè)特點(diǎn):

采用多 Store(與 reflux 類似),Store 實(shí)體與 Component 重合。

由于數(shù)據(jù)流動(dòng)設(shè)計(jì)針對(duì) Component 下的屬性展開,為方便理解,ReRest 的 Store 也可視為雙層結(jié)構(gòu),第一層是 Component 實(shí)體,第二層是 Component 下視作 resource 的屬性定義,包括 props.attrduals.attr

Component 下的 resource,本質(zhì)是數(shù)據(jù),與 Store 同屬一類,Redux 的 reducer 定義,對(duì)應(yīng) ReRest 變成 4 種資源行為定義(讀、寫、setter、listen),而 Redux 的 Action 則弱化成一條操作資源的常規(guī)語句。強(qiáng)調(diào)一句,Redux 設(shè)計(jì)用 Action 提綱挈領(lǐng),ReRest 設(shè)計(jì)用 Resource 提綱挈領(lǐng),弱化 Action 是很自然的事,因?yàn)橄嚓P(guān)操作可以隨時(shí)添加,抓住數(shù)據(jù)定義才是核心本質(zhì)。Redux 編程中,給 Action 指定一個(gè)常量名,再定義 Action 結(jié)構(gòu),然后用 switch..case 到處判斷 action.type,就沒人覺得煩嗎?

6.2 數(shù)據(jù)定義用作事件

偵聽一個(gè) duals.attr 后,偵聽函數(shù)就是事件處理函數(shù),F(xiàn)LUX 框架要求的 Dispatcher 可以簡化,比如我們用 duals.receivedData = data 表示接收到外部一條指令,對(duì)它賦值即觸發(fā)偵聽它的事件處理函數(shù)馬上被調(diào)。

如果對(duì) duals.receivedData 賦值時(shí),新舊值沒有變化,系統(tǒng)將忽略觸發(fā)偵聽函數(shù)。要是不想忽略,調(diào)整一下數(shù)據(jù)定義,比如用 duals.receivedData = [data,ex.time()],加一個(gè)時(shí)間戳,就保證每次對(duì) duals.receivedData 賦值,都能觸發(fā)偵聽函數(shù)了。

盡管 ReRest 聚焦于如何配置資源,duals.attr 的組織形式很簡單,卻完整支持事件流機(jī)制,包括多源頭偵聽,等全部事件來齊后再觸發(fā)回調(diào)函數(shù),例如:

utils.waitAll(comp1,"attr1",comp2,"attr2", function(value1,value2) {
  // do something ...
});
6.3 渲染器

如果一個(gè)節(jié)點(diǎn)的結(jié)構(gòu)比較穩(wěn)定,比如它渲染輸出的標(biāo)簽名不變,其子節(jié)點(diǎn)構(gòu)成也不變,這時(shí),對(duì)該節(jié)點(diǎn)的屬性做 “資源化” 改造很容易。但如果節(jié)點(diǎn)結(jié)構(gòu)不穩(wěn)定,比如,有時(shí)單節(jié)點(diǎn),隨時(shí)變?yōu)槎鄬庸?jié)點(diǎn),甚至有時(shí)輸出的標(biāo)簽名也在變。我們還得另尋方法實(shí)現(xiàn)資源化定義,解決對(duì)策便是 “渲染器”。

在 React 中內(nèi)容組裝在 render() 函數(shù)進(jìn)行,通常由 comp.setState() 驅(qū)動(dòng)render() 函數(shù)反復(fù)調(diào)用。render 是動(dòng)作,按資源化方式理解,把它變名詞,是 rendering,就是渲染器,我們假想 render() 由一個(gè)渲染器驅(qū)動(dòng),渲染器內(nèi)部用一個(gè)計(jì)數(shù)器(記為 id__)控制渲染刷新,比如:comp.duals.id__ = 2 賦值導(dǎo)致 render() 被調(diào)用,運(yùn)行 comp.setState({attr:value}) 也促使 render() 調(diào)用,而且 id__ 會(huì)自動(dòng)取新值。也就是說,每次 render() 運(yùn)行,渲染器的計(jì)數(shù)器都會(huì)自動(dòng)取不同值,等效于執(zhí)行 duals.id__ = value 語句。

按如下方式注冊(cè) duals.id__

01 this.defineDual("id__", function(value,oldValue) {
02   // this.state["tagName."] = "div";
03   // this.state.attr1 = xxx;
04   // this.duals.attr2 = xxx;
05   
06   // prepare jsx_list ...
07   // var jsx_list = [  ...  ];
08   
07   // utils.setChildren(this,jsx_list);
10 });

上述渲染器 duals.id__ 的 setter 函數(shù),我們稱為 idSetter 函數(shù),這種以 “渲染器資源” 指代 render() 渲染過程的定義形式,稱為 idSetter 定義。

在 idSetter 函數(shù)中編寫代碼,等效于在 render() 編程,可以隨意組裝子節(jié)點(diǎn),然后用 utils.setChildren() 設(shè)進(jìn)去。還可修改當(dāng)前節(jié)點(diǎn)的 state.attr, duals.attr,甚至節(jié)點(diǎn)的標(biāo)簽名也可以改,如上面 02 行代碼。

借助 duals.attr 的資源化形式(包括 duals.id__ 渲染器),ReRest 實(shí)現(xiàn)了 render() 渲染過程的范式變換?,F(xiàn)有實(shí)踐表明,基于 ReRest 的編程與 React 原生方式等效,表達(dá)能力近乎等同。

7. 可視化設(shè)計(jì)與 MVVM 框架

為了支持可視化編程,像 JSX 這種與 JS 代碼混寫的界面描述方式需要改進(jìn),因?yàn)榻缑嬖O(shè)計(jì)應(yīng)獨(dú)立進(jìn)行。在可視化設(shè)計(jì)器中,被設(shè)計(jì)的界面,不能像產(chǎn)品正常運(yùn)行那樣表現(xiàn)功能,鼠標(biāo)點(diǎn)擊在可視化設(shè)計(jì)器中表示選擇一個(gè)構(gòu)件,接下來要配置它的屬性,而對(duì)于正式運(yùn)行的產(chǎn)品,可能是按鈕點(diǎn)擊、跳轉(zhuǎn)鏈接點(diǎn)擊等,所以,基于 ReRest 的編程,要求我們改用一種 “功能定義可選捆綁” 的界面描述方式。

shadow-widget 采用 “轉(zhuǎn)義標(biāo)簽” 描述界面,界面的功能實(shí)現(xiàn)則在投影類或 idSetter 函數(shù)中實(shí)施,這兩者分開定義。產(chǎn)品正常運(yùn)行時(shí),在頁面導(dǎo)入初始化階段,兩者自動(dòng)捆綁,讓類似 onClick 在 JS 實(shí)現(xiàn)的功能定義,與用 “轉(zhuǎn)義標(biāo)簽” 描述的界面結(jié)合。但在可視化設(shè)計(jì)狀態(tài),功能定義缺省被忽略(注:也可以不忽略,但要用特殊方式定義)。

上述 “轉(zhuǎn)義標(biāo)簽”,就是用類似

desc
的方式描述非行內(nèi)標(biāo)簽
    desc
,或用類似 title 描述行內(nèi)標(biāo)簽 。上述 “投影類”,與 “idSetter 定義” 等效,都用來定義 Component 節(jié)點(diǎn)的行為。限于本文篇幅,這三項(xiàng)我們不展開介紹。

前面介紹的資源化改造,還支持了 MVVM 框架在 React 技術(shù)體系中得以實(shí)現(xiàn),MVVM 要求數(shù)據(jù)屬性能夠雙向綁定,duals.attrgetter/setter 支持了此項(xiàng)要求。如下圖,ViewModel 就是投影定義與 idSetter 定義,View 是各 Component 從虛擬 DOM 反映到真實(shí) DOM 的界面表現(xiàn),而 Model 是數(shù)據(jù)模型,對(duì)于前端開發(fā),Model 通常很簡單,一般就是各 Component 的 props.attrduals.attr 規(guī)格定義,只有少數(shù)需對(duì)數(shù)據(jù)做轉(zhuǎn)換、存盤、備份等特殊處理的,才會(huì)額外設(shè)計(jì)一個(gè) Model 實(shí)體。

MVVM 可視為 MVC 框架在前端環(huán)境的最佳適配,它也是可視設(shè)計(jì)的基礎(chǔ)。可視化設(shè)計(jì)的主體過程是在創(chuàng)建 Component 構(gòu)件后,在線設(shè)置它的 props.attrduals.attr 屬性值。正因?yàn)?MVVM 中 ViewModel 是雙向綁定的,屬性取值與界面表現(xiàn)才能自動(dòng)保持一致,這也是 MVC 框架不能適應(yīng)前端可視化開發(fā),而 MVVM 適應(yīng)得很好的主要原因。

多說一句,屬性取值與界面表現(xiàn)并非簡單的直接對(duì)應(yīng)關(guān)系,而是屬性取值變更要關(guān)聯(lián)一系列變化,須有自動(dòng) setter 調(diào)用的機(jī)制才行。舉例來說,設(shè)置一個(gè)按鈕的 duals.disabled 為真,不止是設(shè)置 DOM 節(jié)點(diǎn)的 disabled 屬性,還要讓按鈕外觀變灰,再改換 cursor 配置為 "not-allowed"。

8. 函數(shù)式風(fēng)格

相比 Angular 與 Vue,React 生態(tài)鏈上各工具普遍追求純正的函數(shù)式開發(fā),這既與 React 團(tuán)隊(duì)傾向性推動(dòng)有關(guān),也與 React 技術(shù)特征有關(guān),越傾向函數(shù)式開發(fā)就越適應(yīng)它的 FLUX 模型。

8.1 函數(shù)式是 FRP 編程的天然姻親

FLUX 框架是 FRP 編程理念(Functional Reactive Programming)的一種實(shí)現(xiàn),一個(gè)重要技術(shù)路徑是,以 CPS 風(fēng)格(Continuation-Passing Style)應(yīng)對(duì)響應(yīng)式接續(xù)處理。

函數(shù)式編程正是 CPS 變換的最佳載體。舉一個(gè)簡單例子,如下提供 Email 輸入,當(dāng)輸入內(nèi)容不合郵箱格式時(shí),右側(cè)圖標(biāo)出現(xiàn)告警圖標(biāo),底部還有詳細(xì)提示。

響應(yīng)式編程的做法是,用戶持續(xù)輸入文本,內(nèi)容是否合規(guī)隨即校驗(yàn),校驗(yàn)與輸入同時(shí)進(jìn)行,校驗(yàn)結(jié)果并不打斷用戶輸入。這么理解,手工輸入形成持續(xù)的數(shù)據(jù)流,各次數(shù)據(jù)都驅(qū)動(dòng)一次校驗(yàn)處理,校驗(yàn)對(duì)于輸入來說是異步推進(jìn)的。假定用戶輸入合法的 Email 地址后,系統(tǒng)用它自動(dòng)向服務(wù)器查詢進(jìn)一步信息,比如得到用戶別名、上次登錄時(shí)間等,這些信息用來輔助下一步表單填寫??梢赃@么編碼:

01 comp.listen("validation", function(value,oldValue) {
02   if (value == "success") {
03     var sEmail = comp.duals.email;
04     utils.ajax( {
05       url: "/users/" + encodeURIComponent(sEmail),
06       success: function(data) {
07         // ...
08       },
09     });
10   }
11 });

這里 01 行與 06 行調(diào)用都是 CPS 風(fēng)格,實(shí)際調(diào)用雖是異步,但代碼寫一起,上下文變量共享。這種代碼風(fēng)格在響應(yīng)式編程中大量使用,不難看出,函數(shù)式是 FRP 編程的必然選擇。

8.2 ReRest 中的函數(shù)式編程

雖然 “資源” 是靜態(tài)化的概念,但 ReRest 對(duì)資源的動(dòng)作定義,仍是可適用 CPS 的函數(shù)方式,并未破壞整體函數(shù)式風(fēng)格。簡單這么理解,前面所提 ReRest 資源化,實(shí)質(zhì)是提供了 帶錨點(diǎn)的函數(shù)式編程,錨點(diǎn)依附于 Component 實(shí)體而存在。所以,在可視設(shè)計(jì)器中,創(chuàng)建 Component 后,資源錨點(diǎn)(即 props.attrduals.attr)就存在了,這讓所見即所得的在線配置因此成為可能。換一種說法,相當(dāng)于 ReRest 在原設(shè)計(jì)基礎(chǔ)上,插入一排方便思考、易于可視設(shè)計(jì)的 “抓手”。

用來實(shí)現(xiàn) Component 功能定義的投影類,以對(duì)象方式編碼,屬于命令式風(fēng)格。而與之對(duì)等提供功能的 idSetter 定義,是函數(shù)式的,如下舉例:

this.defineDual("id__", function(value,oldValue) {
  if (oldValue == 1) {
    // init process just after all duals-attr registed
  }
  
  if (value <= 2) {
    if (value == 1) { // init process, same to getInitialState()
      // this.setEvent({$onClick:fn});
      // this.defineDual("attr",fn);
      // ...
    }
    else if (value == 2) { // same to componentDidMount()
      // ...
    }
    else if (value == 0) { // same to componentWillUnmount()
      // ...
    }
    return;
  }
  
  // other render process ...
});

前面已介紹 idSetter 如何組裝渲染內(nèi)容,既然渲染器每次計(jì)數(shù)變化代表一次渲染調(diào)用,那能不能留出幾個(gè)特殊計(jì)數(shù)值表達(dá) Component 狀態(tài)變化呢?idSetter 確實(shí)這么做了,比如上面代碼,計(jì)數(shù)值為 0 是初始狀態(tài),變?yōu)?1 是 Component 的雙源屬性尚未預(yù)備的初始化狀態(tài),相當(dāng)于 getInitialState(),變?yōu)?2 是 componentDidMount() 狀態(tài),再變回 0 表示馬上要返回初態(tài),對(duì)應(yīng)于 componentWillUnmount()。這樣,一個(gè)完整的 React Class 定義,我們用一個(gè) idSetter 函數(shù)就表達(dá)了,實(shí)現(xiàn)了命令式風(fēng)格的函數(shù)式表達(dá)。

idSetter 函數(shù)既適應(yīng)可視化設(shè)計(jì)時(shí)界面描述與功能定義分離,還適應(yīng)函數(shù)式編程。比如當(dāng)有多層 Component 嵌套時(shí),你可以將里層 Component 的行為定義任意 “Lifting State Up” 到外層 Component 的函數(shù)空間。

8.3 Lifting State Up

采用 JSX 描述界面時(shí),行為定義與虛擬 DOM 描述混在一起,這時(shí)僅依賴 props.attr 逐層傳遞實(shí)現(xiàn)數(shù)據(jù)共享方式,用起來很不方便。React 官方介紹提供一種 “上舉 State” 的解決方案,以輸入溫度值判斷是否達(dá)到沸點(diǎn)為例,參見 Lifting State Up。

將上舉 State 用在 ReRest 編程中,除了收獲 React 官方所提幾個(gè)好處,還有兩項(xiàng)特別收益。其一,原有 React 基于一個(gè)過程組織渲染內(nèi)容,而 ReRest 主體是基于 duals.attr 資源驅(qū)動(dòng)渲染,跨節(jié)點(diǎn) listen 更容易,處理邏輯也更清晰;其二,定義節(jié)點(diǎn)行為的 idSetter 是函數(shù),原生 React Class 定義要用 class MyClass extends React.Component {} 方式,層層嵌套使用時(shí),肯定沒有 idSetter 用得方便。

如果仔細(xì)琢磨 “Lifting State Up” 方案,大家不難發(fā)現(xiàn),上舉 State 解決了部分 Reflux 或 Redux 已支持的需求,被上舉共享的 state 其實(shí)也是一種 Store 數(shù)據(jù)。

9. 可視化設(shè)計(jì)實(shí)踐

ReRest 編程在 shadow-widget 平臺(tái)的實(shí)踐已持續(xù)一年多時(shí)間,多個(gè)項(xiàng)目采用了 ReRest 編程,較典型的有 pinp-blog 與 shadow-bootstrap。在這一年多時(shí)間里,shadow-widget 底層庫也在 ReRest 實(shí)踐推動(dòng)下不斷完善,尤其是 idSetter 與可計(jì)算表達(dá)式方面,優(yōu)化幅度較大。

在接下來幾節(jié),我們補(bǔ)充介紹前文尚未涉及的,與實(shí)踐相關(guān)的若干知識(shí)與編程體驗(yàn)。

9.1 正交框架分析模式

先介紹 “功能塊” Functionarity Block(簡稱 FB)的概念。一組 Component 節(jié)點(diǎn)合起來提供某專項(xiàng)功能,稱為一個(gè) FB。以上面提到 Lifting State Up 判斷溫度是否達(dá)到沸點(diǎn)為背景,我們可以開發(fā)兩個(gè)功能塊,其一是配置溫度格式(config FB),用來配置當(dāng)前采用攝氏 Celsius 還是華氏 Fahrenheit 作計(jì)量單位,其二是計(jì)算沸點(diǎn)(calculator FB),提供輸入框,判斷輸入溫度是否達(dá)到沸點(diǎn)。

后一 FB 的界面如下:

編寫 FB 代碼塊如下:

(function() { // functionarity block: calculator

var scaleNames = { c:"Celsius", f:"Fahrenheit" };
var selfComp = null, verdictComp = null;

idSetter["calculator"] = function(value,oldValue) {
  // ...
};

})();

一個(gè) FB 宜用一個(gè)函數(shù)包裹,主要為了構(gòu)造獨(dú)立的命名空間(Namespace),本功能塊內(nèi)共享的變量在這個(gè)地方定義,比如上面代碼中 scaleNames, selfComp, verdictComp 變量,把命名空間獨(dú)立出來,也防止 FB 內(nèi)部使用的變量污染外部全局空間。

既然一個(gè) FB 內(nèi)某些 Component 很常用,把它定義成 FB 內(nèi)共享的變量會(huì)更方便。

var selfComp = null, verdictComp = null;

idSetter["calculator"] = function(value,oldValue) {
  if (value <= 2) {
    if (value == 1) {      // init
      selfComp = this;
      // ...
    }
    else if (value == 2) { // mount
      verdictComp = this.componentOf("verdict");
      // ...
    }
    else if (value == 0) { // unmount
      selfComp = verdictComp = null;
    }
    return;
  }
};

產(chǎn)品開發(fā)明顯可分兩個(gè)階段:界面可視化設(shè)計(jì)與功能實(shí)現(xiàn),在前一階段,應(yīng)考慮有哪些 FB 功能塊可分解,再針對(duì)各 FB 設(shè)計(jì)界面,按用戶使用習(xí)慣逐級(jí)擺放各構(gòu)件,各層構(gòu)件都是 W 樹中節(jié)點(diǎn)。以上述 config 與 calculator 功能塊為例,我們畫出 FB 分布為橫軸,W 樹為縱軸的示例圖。

之后進(jìn)入開發(fā)第二階段:功能實(shí)現(xiàn)。這時(shí)要解決數(shù)據(jù)如何在 FB 之間流動(dòng),前一功能塊 config 配置當(dāng)前采用哪種溫度格式,記錄到 duals.scale,后一功能塊 calculator 根據(jù)自身 duals.scale 配置指示界面如何顯示,并決定用 100 度還是 212 度判斷沸點(diǎn),兩個(gè) scale 屬性的數(shù)據(jù)流向如下圖,我們只需讓后一 duals.scale 偵聽前一 duals.scale,即實(shí)現(xiàn)兩者自動(dòng)同步。

本處舉例比較簡單,復(fù)雜些產(chǎn)品的設(shè)計(jì)過程大致也是這幾個(gè)步驟。

總結(jié)一下,整個(gè) HTML 頁面是一顆 DOM 樹,是縱向的(上圖縱軸),將這顆樹劃分為若干 FB 功能塊(上圖橫軸),劃分過程主要依據(jù) MVVM 逐步拆解;而處理各功能塊之間的橫向聯(lián)系,則以 FRP 思路為主導(dǎo)。這一縱一橫的思考方式,我們稱為 “正交框架” 分析模式。

可視化設(shè)計(jì)時(shí),提供在線配置的最小單位是各 Component 的 props.attrduals.attr,就是 ReRest 所說的 “資源” 項(xiàng)。而處理各 FB 之間數(shù)據(jù)如何流動(dòng)的思考起點(diǎn),也是這類 “資源” 項(xiàng),MVVM 與 FRP 分析的交匯處正是 ReRest 資源化的落腳點(diǎn)。

9.2 Component 屬性定位的變化

props.attr 是只讀的,用來驅(qū)動(dòng)本節(jié)點(diǎn)組織渲染數(shù)據(jù),凡涉及狀態(tài)變化的要用 state.attr,然后同樣用 props 驅(qū)動(dòng)子節(jié)點(diǎn)的內(nèi)容更新。現(xiàn)有 React 生態(tài)鏈上各類工具對(duì) props.attr 定位似乎只有兩項(xiàng):一是用作 Component 的入口驅(qū)動(dòng)數(shù)據(jù),二是以只讀特性保障數(shù)據(jù)單向流動(dòng)。

shadow-widget 對(duì) props 與 state 的使用定位做了優(yōu)化。其一,用 duals.attr 表達(dá)一個(gè) Component 對(duì)外公開的控制接口,不再建議用 setState() 動(dòng)態(tài)更新 “非自身節(jié)點(diǎn)” 的數(shù)據(jù)了,相應(yīng)的 state.attr 也收縮到 “只供 Component 內(nèi)部編程” 時(shí)使用,類似于用作私有變量。其二,props.attr 當(dāng)入口驅(qū)動(dòng)數(shù)據(jù)的定位沒變,但刨去轉(zhuǎn)換成 duals.attr 與事件函數(shù),剩下的常規(guī)屬性在生存周期內(nèi)被看作常量,在節(jié)點(diǎn) unmount 之前不會(huì)變化。

這兩點(diǎn)定位調(diào)整的背后有深刻原因,開發(fā)理念變了。在 React 支持的虛擬 DOM 庫級(jí)別,各 Component 所有屬性都是對(duì)等的,無差別,虛擬節(jié)點(diǎn)無需識(shí)別各項(xiàng)屬性的語法含義,在底層這么處理沒問題,因?yàn)樽鳛榈讓訋?,只聚焦?jié)點(diǎn)虛擬化。但對(duì)于上層應(yīng)用,須區(qū)分各屬性的語義,現(xiàn)實(shí)應(yīng)用中,各節(jié)點(diǎn)總具備一定 “性狀” 的。比如,你想表達(dá)一段文本就創(chuàng)建

節(jié)點(diǎn);如果創(chuàng)建了

    節(jié)點(diǎn),也意味著你將在它下面掛入
  • 節(jié)點(diǎn);如果創(chuàng)建 節(jié)點(diǎn),通常連帶 type 屬性也算作 “性狀” 一部分,type="text" 文本框,type="checkbox" 是選項(xiàng)框,兩者形態(tài)差異巨大,文本框要用 node.value 取輸入字串,選項(xiàng)框則用 node.checked。

    所以,上層應(yīng)用宜將各節(jié)點(diǎn)的固有性狀,視作生存期內(nèi)不變的常量,動(dòng)態(tài)變化的納入 duals,用作控制量。反之,如果不承認(rèn)節(jié)點(diǎn)固有性狀,就不會(huì)有 MVVM 框架形式,可視設(shè)計(jì)器也無法支持通過拖入樣板來創(chuàng)建 Component。比如假設(shè)你創(chuàng)建的是

    節(jié)點(diǎn),改改屬性就把它變成
      列表,可視設(shè)計(jì)就沒法做了。

      shadow-widget 還將 className 分裂成 props.classNameduals.klass 兩個(gè)屬性,用 className 表達(dá)固有類定義,在構(gòu)件的生存期內(nèi)不變,用 klass 表達(dá)可變的狀態(tài)量。

      9.3 父子結(jié)節(jié)的單向依賴

      我們先看一個(gè)事實(shí),Bootstrap 提供的 50 多個(gè)組件中,大部分由多層節(jié)點(diǎn)構(gòu)成,或者使用時(shí)要求與其它組件搭配,一個(gè)節(jié)點(diǎn)表達(dá)完整功能的只是少數(shù),而且都只提供簡單功能,像 Label、Badge 等,這類組件約占總量十分之一。可以說,現(xiàn)實(shí)中的前端開發(fā),父子 Component 組合是常態(tài),是主流。

      Shadow Widget 有很多機(jī)制讓父子節(jié)點(diǎn)關(guān)聯(lián)起來,主要有:

      把所有存活的構(gòu)件(已掛載且未卸載)串接成一顆 W 樹,樹中各節(jié)點(diǎn)能方便的互相引用

      提供導(dǎo)航面板把多個(gè)構(gòu)件封裝起來,形成一組,組內(nèi)構(gòu)件用 "./" 相對(duì)路徑索引

      上面提到 FB 功能塊的編碼,建立塊內(nèi)共用 Namespace,讓功能緊密相關(guān)的父子節(jié)點(diǎn)共享變量

      $for, $if, $else 等指令描述動(dòng)態(tài)節(jié)點(diǎn),層層嵌套的 callspace 支持在下級(jí)節(jié)點(diǎn)直接引用上級(jí)各層節(jié)點(diǎn)的各種屬性

      支持 $trigger 機(jī)制觸發(fā)相鄰節(jié)點(diǎn)的動(dòng)作定義

      React 讓 props 屬性只讀的深刻根源是:解決數(shù)據(jù)依賴性。解決依賴性的同時(shí),順帶保證數(shù)據(jù)在父子節(jié)點(diǎn)之間要單向流動(dòng)。節(jié)點(diǎn)創(chuàng)建有先有后,具有從屬關(guān)系的兩個(gè)節(jié)點(diǎn),子節(jié)點(diǎn)必然在父節(jié)點(diǎn)之后創(chuàng)建,并且 unmount 必在父節(jié)點(diǎn)之前,也就是,子節(jié)點(diǎn)依賴于父節(jié)點(diǎn)而存在,子節(jié)點(diǎn)的數(shù)據(jù)也依賴于父節(jié)點(diǎn)的屬性先行賦值。所以,React 設(shè)計(jì)了數(shù)據(jù)傳遞要借助 props 逐層進(jìn)行,原則上屬性數(shù)據(jù)跨層不可見(先撇開 context 不談,那是補(bǔ)救性設(shè)計(jì),官方并不推薦你用)。

      子節(jié)點(diǎn)依賴于父節(jié)點(diǎn),但反過來不是,依賴是單向的,但 React 生態(tài)鏈上諸多工具,都按 “隔絕依賴” 來處理了,相當(dāng)于忽略了單向依賴存在。舉例來說,比方我們要設(shè)計(jì)下圖 DropdownBtn 與 SplitBtn 兩種按鈕,兩者功能基本一樣,外觀有差別,怎么實(shí)現(xiàn)呢?

      外層節(jié)點(diǎn)用 this.isSplitBtn 指示按鈕是否為 SplitBtn,然后里層節(jié)點(diǎn)根據(jù) isSplitBtn 取值,繪制不同外觀的按鈕。如果按 “隔絕依賴” 來處理,只能借助 props 屬性層層傳遞 isSplitBtn,隔了幾層就傳幾層;如果按 “單向依賴” 來處理,里層哪個(gè)節(jié)點(diǎn)需要要區(qū)分 isSplitBtn,就往上層查找,看看 props.isSplitBtn 取什么值。這兩種處理方式差別很大,前者忽略了主從構(gòu)件的天然關(guān)系,以暴露接口的代價(jià)實(shí)現(xiàn)功能,把無關(guān)節(jié)點(diǎn)都牽扯進(jìn)當(dāng)來傳手,就像打排球的一傳、二傳、三傳,當(dāng)功能組合較多時(shí),顯得很繞。

      從子節(jié)點(diǎn)向上查找,分析一級(jí)(或多級(jí))父節(jié)點(diǎn)的屬性特點(diǎn),從而確定它自身所處的場景,進(jìn)而讓當(dāng)前節(jié)點(diǎn)應(yīng)對(duì)不同場景表現(xiàn)不同功能。我們管這種場景推導(dǎo)過程叫 “場景自省”,如上介紹,向上追溯的 “場景自省” 是安全的,因?yàn)樽庸?jié)點(diǎn)若存活,父節(jié)點(diǎn)必然還存活,反過來從父節(jié)點(diǎn)查子節(jié)點(diǎn)則不行。

      9.4 不繞彎也是生產(chǎn)力

      現(xiàn)有 React 生態(tài)鏈上諸多主流工具都很繞,不像 shadow-widget 那么直接,主要表現(xiàn)以下幾個(gè)方面。

      其一,主流工具普遍忽視父子節(jié)點(diǎn)的主從關(guān)系是隱含豐富信息的,把所有 Component 擺同等位置來解決跨節(jié)點(diǎn)數(shù)據(jù)傳遞問題。

      源頭在于 Facebook 官方的 FLUX 框架有缺陷,F(xiàn)LUX 在虛擬 DOM 的上層實(shí)現(xiàn),但它繼續(xù)無視 Component 屬性帶語義特性,都無差別對(duì)待。借助 Dispatcher 分發(fā) Action,構(gòu)造獨(dú)立的 Store,統(tǒng)一處理各 Action 消息。另設(shè) Store 與 Action 另行驅(qū)動(dòng)的過程,相當(dāng)于換個(gè)地方重建各節(jié)點(diǎn)的場景信息。

      其二,這些工具普遍過于依賴函數(shù)式風(fēng)格,靜態(tài)化概念只停留在 Component 層面,沒往下探一層。各 Component 互相關(guān)聯(lián),形成網(wǎng)格,這網(wǎng)格直接用函數(shù)式編程去編織了。因?yàn)榇a量沒減,該做的事情一件不少,重建場景的各個(gè)處理環(huán)節(jié)又衍生不少概念,比較繞。基于 ReRest 的編程則將 Component 下的屬性視作資源,把靜態(tài)化概念深入一層,然后在 “資源粒子” 層面,用函數(shù)式風(fēng)格編織網(wǎng)格。這樣更直接了當(dāng),也符合開發(fā)者思考習(xí)慣。

      shadow-bootstrap 項(xiàng)目按 ReRest 理念去實(shí)踐的,該項(xiàng)目核心功能是將 Bootstrap 往 shadow-widget 平臺(tái)適配。與之類似,業(yè)界還有一個(gè)知名項(xiàng)目 react-bootstrap,把 Bootstrap 往 React 適配。這兩項(xiàng)目的功能對(duì)等,封裝的組件幾乎能一一對(duì)應(yīng),如果對(duì)比兩者源碼,shadow-bootstrap 明顯簡潔許多,react-bootstrap 不容易讀,繞來繞去的。最終代碼 minify 后,前者 103 Kb,而后者 213 Kb,整整多出一倍。前者開發(fā)只用一個(gè)多月,后者遠(yuǎn)不止這個(gè)投入,當(dāng)我們的框架沒那么繞時(shí),生產(chǎn)力是大幅提升的。

      10. 總結(jié)

      長期以來 GUI 開發(fā)工具與 Web 前端工具是兩條獨(dú)立主線,并行發(fā)展。MFC、Delphi、VB、WxWidget、Qt 等歸入前者,沒人將前端開發(fā)也視作 GUI 一類,不過,大概沒人否認(rèn)前端開發(fā)主要工作是設(shè)計(jì)圖形用戶界面(Graphical User Interface),就目的而言,前端開發(fā)無疑也是 GUI 開發(fā)。

      這兩條主線靠攏發(fā)展的時(shí)代已來臨,虛擬 DOM 技術(shù)結(jié)合 FRP 理念,再結(jié)合 ReRest 資源化改造,基于 MVVM 框架 —— 對(duì)應(yīng)主流 GUI 工具的 MVC —— 的可視化開發(fā)已經(jīng)走通了。ReRest 方法論嘗試讓前端開發(fā)回歸可視化 GUI 工具序列,其實(shí)踐已在 shadow-widget 平臺(tái)走出第一步,希望這一步對(duì) Web APP 與 Native APP 逐步融合的發(fā)展提供有益經(jīng)驗(yàn)。

      ?

      (本文完)

      文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

      轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/89245.html

      相關(guān)文章

      • 2017-10-23 前端日?qǐng)?bào)

        摘要:前端日?qǐng)?bào)精選桌面通知精讀前端性能優(yōu)化備忘錄聊聊組件間通信的幾種姿勢到底該如何配置深入理解高階組件中文第期體系調(diào)研報(bào)告前端面試總結(jié)掘金技術(shù)周刊期知乎專欄從試著改進(jìn)可重用做起掘金式數(shù)學(xué)作者眾成翻譯為什么企業(yè)進(jìn)行數(shù)碼變革要用平臺(tái)眾成 2017-10-23 前端日?qǐng)?bào) 精選 HTML5 桌面通知:Notification API精讀《2017前端性能優(yōu)化備忘錄》聊聊Vue.js組件間通信的幾種姿...

        mengbo 評(píng)論0 收藏0
      • React 視化開發(fā)工具 Shadow Widget 非正經(jīng)入門(之一:React 三宗罪)

        摘要:前言非正經(jīng)入門是相對(duì)正經(jīng)入門而言的。不過不要緊,正式學(xué)習(xí)仍需回到正經(jīng)入門的方式。快速入門建議先學(xué)會(huì)用拼文寫文檔注冊(cè)一個(gè)賬號(hào),把庫到自己名下,然后用這個(gè)庫寫自己的博客,參見這份介紹。會(huì)用拼文寫文章,相當(dāng)于開發(fā)已入門三分之一了。 本系列博文從 Shadow Widget 作者的視角,解釋該框架的設(shè)計(jì)要點(diǎn),既作為用戶手冊(cè)的補(bǔ)充,也從更本質(zhì)角度幫助大家理解 Shadow Widget 為什么這...

        dongxiawu 評(píng)論0 收藏0
      • 介紹一項(xiàng)讓 React 可以 Vue 抗衡技術(shù)

        摘要:明明如日中天,把它與倒過來,給加點(diǎn)東西或可與抗衡。在之后,大版本有十?dāng)?shù)個(gè),只有最近推的才回歸正常等后人總結(jié)歷史,無疑會(huì)把與之間的所有都稱為垃圾。讓網(wǎng)頁支持所見即得的可視化設(shè)計(jì),是框架的最高形態(tài),以前沒有類似工具,主要因?yàn)榧夹g(shù)做不到。 好吧,我承認(rèn)我是標(biāo)題黨。React 明明如日中天,把它與 Vue 倒過來,給 Vue 加點(diǎn)東西或可與 React 抗衡。不過,這兩年 Vue 干的正是這事...

        icattlecoder 評(píng)論0 收藏0

      發(fā)表評(píng)論

      0條評(píng)論

    閱讀需要支付1元查看
    <samp id="ceaa6"></samp>
    <samp id="ceaa6"><tbody id="ceaa6"></tbody></samp>
    <