天天看點

在 React 工程中利用 Mota 編寫面向對象的業務模型

React 是一個「視圖層」的 UI 架構,以常見的 MVC 來講 React 僅是 View,而我們在編寫應用時,通常還需要關注更加重要的 model,對于 React 來講,我們常常需要一個「狀态管理」庫。然而,目前大多數針對 React 的狀态管理庫都是「強依賴」過多的侵入本應該獨立的業務模型中,導緻「業務邏輯」對應的代碼并不能輕易在其它地方重用,往往這些架構還具有「強排它性」,但是「業務模型」應該是沒有過多依賴,應該是無關架構的,它應該随時可以被用在任何合适的 JavaScript 環境中,使用 mota 你可以用原生的普通的 JavaScript 代碼編寫你的「業務模型」,并讓你的「業務模型」在不同架構、不同運作環境下重用更為容易。

mota 是一個主張「面向對象」的、支援「雙向綁定」的 React 應用輔助庫,基于 mota 你可以用純 JavaScript 為應用編寫完全面向對象的「業務模型」,并輕易的将「業務模型」關聯到 React 應用中。

<a href="http://houfeng.net/dn-template-mota/example/">線上 TodoList 示例</a>

通過 npm 安裝,如下

或通過 <code>dawn</code> 腳手腳加建立工程,如下

一個 <code>mota</code> 工程的通常結構如下

在 mota 中「模型」可以是由一個 <code>class</code> 或普通的的 <code>Object</code>,整個「業務模型層」會由多個 <code>class</code> 和多個 <code>Object</code> 組成,而編寫模型所需要的知識就是 JavaScript 固有的面向對象程式設計的知識。

如下示例通過編寫一個名為 <code>User</code> 的 <code>class</code> 建立了一個「使用者模型」

也可以是一個 <code>Object</code>,通常這個模型需要是「單例」時,可采用這種方式,如下

在「業務模型」編寫完成後,可以通過 <code>@model</code> 将某個「類」或「類的執行個體」關聯到指定元件,關聯後便可以在元件中使用 <code>this.model</code> 通路「模型的成員變量或方法」了,mota 還會自動「收集元件依賴」,在元件「依賴的模型資料」發生變化時,自動響應變化并「驅動元件重新渲染」,如下

值得注意的是,在使用 <code>@model</code> 時如果傳入的是一個 <code>class</code> 最終每個元件執行個體都會自動建立一個 <code>獨立的執行個體</code>,這樣帶來的好處是「當一個頁面中有同一個元件的多個執行個體時,不會互相影響」。

在 React 中通常會将應用折分為多個元件重用它們,并在用時傳遞給它「屬性」,mota 提供了将「元件屬性」映射到「模型資料」的能力,基于 <code>model</code> 程式設計會讓「視圖層」更單一,專注于 UI 的呈現,,如下

上邊的代碼通過 <code>mapping</code> 将 <code>Demo</code> 這個元件的 <code>value</code> 屬性映射到了 <code>model.value</code> 上,在元件的屬性 <code>value</code> 發生變化時,會自動同步到 <code>model.value</code> 中。

通過一個 map 進行映射,還可以讓「元件屬性」和「模型的成員」使用不同名稱,如下:

上邊的代碼,将元件 demo 的 <code>content</code> 屬性映射到了 <code>model.value</code> 上。

mota 中提供了一個 <code>autorun</code> 函數,可用于裝飾 React 元件的成員方法,被裝飾的「成員方法」将會在元件挂載後自動執行一次,mota 将「收集方法中依賴的模型資料」,在依賴的模型資料發生變化時會「自動重新執行」對應的元件方法。

示例

上邊的示例代碼中,元件在被挂載後将會自動執行 <code>test</code> 方法,同時 mota 會發現方法中依賴了 <code>model.name</code>,那麼,在 <code>model.name</code> 發生變化時,就會重新執行 <code>test</code> 方法。

mota 中提供了一個 <code>watch</code> 函數,可用于裝飾 React 元件的成員方法,<code>watch</code> 可以指定要觀察的「模型資料」,在模型資料發變化時,就會自動執行「被裝飾的元件方法」,<code>watch</code> 還可以像 <code>autorun</code> 一樣自動執行一次,但它和 <code>autorun</code> 還是不盡相同,主要有如下差別

<code>autorun</code> 會自動收集依賴,而 <code>watch</code> 不會關心元件方法中有何依賴,需要手動指定依賴的模型資料

<code>watch</code> 預設不會「自動執行」,需顯式的指定「立即執行參數為 true」,才會自動執行首次。

<code>autorun</code> 依賴的是「模型資料」本身,而 <code>watch</code> 依賴的是「計算函數」每次的「計算結果」

上邊的代碼,通過 <code>watch</code> 裝飾了 <code>test</code> 方法,并指定了觀察的模型資料 <code>model.name</code>,那麼每當 <code>model.name</code> 發生變化時,都會列印 <code>name 發生了變化</code>.

<code>watch</code> 是否重新執行,取決于 <code>watch</code> 的作為第一個參數傳給它的「計算函數」的計算結果,每當依賴的模型資料發生變化時 <code>watch</code> 都會重執行計算函數,當計算結果有變化時,才會執行被裝飾的「元件方法」,示例

有時,我們希望 <code>watch</code> 能首先自動執行一次,那麼可通過向第二個參數傳一個 <code>true</code> 聲明這個 <code>watch</code> 要自動執行一次。

上邊的 <code>test</code> 方法,将會在「元件挂載之後自動執行」,之後在 <code>model.name</code> 發生變化時也将自動重新執行。

不要驚詫,就是「雙向綁定」。<code>mota</code> 主張「面向對象」,同樣也不排斥「雙向綁定」,使用 mota 能夠實作類似 <code>ng</code> 或 <code>vue</code> 的綁定效果。還是前邊小節中的模型,我們來稍微改動一下元件的代碼

其中的「關鍵」就是 <code>@binding</code>,使用 <code>@binding</code> 後,元件便具備了「雙向綁定」的能力,在 <code>jsx</code> 中便可以通過名為 <code>data-bind</code> 的自定義 <code>attribute</code> 進行綁定了,<code>data-bind</code> 的值是一個「綁定表達式字元串」,綁定表達式執行的 <code>scope</code> 是 <code>model</code> 而不是 <code>this</code>,也就是隻能與 <code>模型的成員</code> 進行綁定。

會有一種情況是當要綁定的資料是一個循環變量時,「綁定表達式」寫起會較麻煩也稍顯長,比如

因為「綁定表達式」的執行 <code>scope</code> 預設是 <code>this.model</code>,以及「表達式是個字元串」,看一下 <code>userList[${index}].selected</code> 這并不友好,為此 mota 還提供了一個名為 <code>data-scope</code> 的 <code>attribute</code>,通過它能改變要綁定的 <code>scope</code>,參考如下示例

通過 <code>data-scope</code> 将 <code>input</code> 的綁定上下文對象聲明為目前循環變量 <code>user</code>,這樣就可以用 <code>data-bind</code> 直接綁定到對應 <code>user</code> 的屬性上了。

所有的原生表單控件,比如「普通 input、checkbox、radio、textarea、select」都可以直接進行綁定。其中,「普通 input 和 textrea」比較簡單,将一個字元類型的模型資料與控件綁定就行了,而對于「checkbox 和 radio」 有多種不同的綁定形式。

将「checkbox 或 radio」綁定到一個 <code>boolean</code> 值,此時會将 checkbox 或 radio 的 checked 屬性和模型資料建立綁定,checked 反應了 <code>boolean</code> 變量的值,參考如下示例

如上示例通過 <code>this.model.selected</code> 就能拿到目前 checkbox 或 radio 的選中狀态。

将 checkbox 綁定到一個「數組」,通常是多個 checkbox 綁定同一個數組變量上,此時和資料建立綁定的是 checkbox 的 value,資料中會包含目前選中的 checkbox 的 value,如下

如上示例,通過 <code>this.selected</code> 就能知道目前有哪些 checkbox 被選中了,并拿到所有選中的 value

将多個 radio 綁定我到一個「字元類型的變量」,此時和資料建立綁定的是 raido 的 value,因為 radio 是單選的,是以對應的資料是目前選中的 radio 的 value,如下

通過 <code>this.model.selected</code> 就能拿到目前選中的 radio 的 value

但是對于一些「元件庫」中的「部分表單元件」不能直接綁定,因為 mota 并沒有什麼依據可以判斷這是一個什麼元件。是以 mota 提供了一個名為 <code>bindable</code> 的函數,用将任意元件包裝成「可綁定元件」。

bindable 有兩種個參數,用于分别指定「原始元件」和「包裝選項」

關建是 <code>bindable</code> 需要的 <code>opts</code>,通過 <code>opts</code> 我們可以造訴 mota 如何綁定這個元件,<code>opts</code> 中有兩個重要的成員,它的結構如下

是以,我們可以這樣包裝一個自定義文本輸入框

對這種「value 不需要轉換,change 能通過 event 或 event.target.value 拿到值」的元件,通過如上的代碼就能完成包裝了。

對于有 <code>onChange</code> 和 <code>value</code> 的這類文本輸入元件,因為 opts 的預設值就是

是以,可以更簡單,這樣就行,

而對于 checkbox 和 radio 來講,如上邊講到的它「根據不同的資料型有不同的綁定形式」,這就需要指定處理函數了,如下

通過 <code>prop</code> 的第二個值,能指定「屬性處理函數」,event 的第二個值能指取「事件處理函數」,處理函數的 <code>ctx</code> 是個特殊的對象

<code>ctx.getValue</code> 能擷取「目前綁定的模型資料」

<code>ctx.setValue</code> 能設定「目前綁定的模型資料」

上邊是 <code>radio</code> 的配置,首先,在「屬性處理函數」中通過綁定的「模型資料的類型」決定 <code>checked</code> 最終的狀态是什麼,并在函數中傳回。再次,在「事件處理函數」中通過綁定的「模型資料的類型」決定将什麼值回寫到模型中。

通過「屬性處理函數」和「事件處理函數」幾乎就能将任意的自定義元件轉換為「可綁定元件」了。

另外,對于常見的 <code>CheckBox</code> 和 <code>Radio</code> 類型的元件 mota 也提供了内建的 <code>opts</code> 配置支援,如果一個自定義元件擁有和「原生 checkbox 一緻的屬性和事件模型」,那邊可以直接用簡單的方式去包裝,如下

好了,關于綁定就這些了。

<a href="http://houfeng.net/mota/#!/zh/guide/quick">快速開始</a>

<a href="http://houfeng.net/mota/#!/zh/guide/model">編寫業務模型</a>

<a href="http://houfeng.net/mota/#!/zh/guide/mapping">将元件屬性映射到模型</a>

<a href="http://houfeng.net/mota/#!/zh/guide/autorun">自執行函數</a>

<a href="http://houfeng.net/mota/#!/zh/guide/watch">監聽模型變化</a>

<a href="http://houfeng.net/mota/#!/zh/guide/binding">将模型資料與表單綁定</a>

<a href="https://github.com/Houfeng/mota">開源位址</a>

<a href="https://github.com/Houfeng/mota/releases">版本釋出日志</a>

<a href="https://tldrlegal.com/license/mit-license">MIT 開源協定</a>

繼續閱讀