在React工程中利用Mota編寫物件導向的業務模型
簡述
React 是一個「檢視層」的 UI 框架,以常見的 MVC 來講 React 僅是 View,而我們在編寫應用時,通常還需要關注更加重要的 model,對於 React 來講,我們常常需要一個「狀態管理」庫。然而,目前大多數針對 React 的狀態管理庫都是「強依賴」過多的侵入本應該獨立的業務模型中,導致「業務邏輯」對應的程式碼並不能輕易在其它地方重用,往往這些框架還具有「強排它性」,但是「業務模型」應該是沒有過多依賴,應該是無關框架的,它應該隨時可以被用在任何合適的 JavaScript 環境中,使用 mota 你可以用原生的普通的 JavaScript 程式碼編寫你的「業務模型」,並讓你的「業務模型」在不同框架、不同執行環境下重用更為容易。
mota 是一個主張「物件導向」的、支援「雙向繫結」的 React 應用輔助庫,基於 mota 你可以用純 JavaScript 為應用編寫完全物件導向的「業務模型」,並輕易的將「業務模型」關聯到 React 應用中。
示例
安裝
通過 npm 安裝,如下
$ npm i mota --save
或通過 dawn
腳手腳加建立工程,如下
$ mkdir your_path
$ cd your_path
$ dn init -t mota
$ dn dev
需要先安裝 dawn(Dawn 安裝及使用文件)
工程結構
一個 mota
工程的通常結構如下
.
├── README.md
├── package.json
└── src
├── assets
│ ├── common.less
│ ├── favicon.ico
│ └── index.html
├── components
│ ├── todoApp.js
│ └── todoItem.js
├── index.js
└── models
├── TodoItem.js
├── TodoList.js
└── index.js
編寫業務模型
在 mota 中「模型」可以是由一個 class
或普通的的 Object
,整個「業務模型層」會由多個 class
和多個 Object
組成,而編寫模型所需要的知識就是 JavaScript 固有的物件導向程式設計的知識。
如下示例通過編寫一個名為 User
的 class
建立了一個「使用者模型」
export default class User {
firstName = `Jack`;
lastName = `Hou`;
get fullName(){
reutrn `${this.firstName} ${this.lastName}`;
}
}
也可以是一個 Object
,通常這個模型需要是「單例」時,可採用這種方式,如下
export default {
firstName: `Jack`,
lastName: `Hou`,
get fullName(){
reutrn `${this.firstName} ${this.lastName}`;
}
};
在「業務模型」編寫完成後,可以通過 @model
將某個「類」或「類的例項」關聯到指定元件,關聯後便可以在元件中使用 this.model
訪問「模型的成員變數或方法」了,mota 還會自動「收集元件依賴」,在元件「依賴的模型資料」發生變化時,自動響應變化並「驅動元件重新渲染」,如下
import { model,binding } from `mota`;
import React from `react`;
import ReactDOM from `react-dom`;
import User from `./models/user`;
@model(User)
class App extends React.Component {
onChange(field,event){
this.model[field] = event.target.value;
}
render(){
return <div>
<p>{this.model.fullName}</p>
<p>
<input onChange={this.onChange.bind(this,`firstName`)}/>
<br/>
<input onChange={this.onChange.bind(this,`lastName`)}/>
</p>
</div>;
}
}
ReactDOM.render(<App/>, mountNode);
值得注意的是,在使用 @model
時如果傳入的是一個 class
最終每個元件例項都會自動建立一個 獨立的例項
,這樣帶來的好處是「當一個頁面中有同一個元件的多個例項時,不會相互影響」。
屬性對映
在 React 中通常會將應用折分為多個元件重用它們,並在用時傳遞給它「屬性」,mota 提供了將「元件屬性」對映到「模型資料」的能力,基於 model
程式設計會讓「檢視層」更單一,專注於 UI 的呈現,,如下
@model({ value: `demo` })
@mapping([`value`])
class Demo extends React.Component {
render () {
return <div>{this.model.value}</div>;
}
}
上邊的程式碼通過 mapping
將 Demo
這個元件的 value
屬性對映到了 model.value
上,在元件的屬性 value
發生變化時,會自動同步到 model.value
中。
通過一個 map 進行對映,還可以讓「元件屬性」和「模型的成員」使用不同名稱,如下:
@model({ value: `demo` })
@mapping({ content: `value` })
class Demo extends React.Component {
render () {
return <div>{this.model.value}</div>;
}
}
上邊的程式碼,將元件 demo 的 content
屬性對映到了 model.value
上。
自執行函式
mota 中提供了一個 autorun
函式,可用於裝飾 React 元件的成員方法,被裝飾的「成員方法」將會在元件掛載後自動執行一次,mota 將「收集方法中依賴的模型資料」,在依賴的模型資料發生變化時會「自動重新執行」對應的元件方法。
示例
import { Component } from `react`;
import { model, autorun } from `mota`;
import DemoModel from `./models/demo`;
@model(DemoModel)
export default Demo extends Component {
@autorun
test() {
console.log(this.model.name);
}
}
上邊的示例程式碼中,元件在被掛載後將會自動執行 test
方法,同時 mota 會發現方法中依賴了 model.name
,那麼,在 model.name
發生變化時,就會重新執行 test
方法。
監聽模型變化
mota 中提供了一個 watch
函式,可用於裝飾 React 元件的成員方法,watch
可以指定要觀察的「模型資料」,在模型資料發變化時,就會自動執行「被裝飾的元件方法」,watch
還可以像 autorun
一樣自動執行一次,但它和 autorun
還是不盡相同,主要有如下區別
-
autorun
會自動收集依賴,而watch
不會關心元件方法中有何依賴,需要手動指定依賴的模型資料 -
watch
預設不會「自動執行」,需顯式的指定「立即執行引數為 true」,才會自動執行首次。 -
autorun
依賴的是「模型資料」本身,而watch
依賴的是「計算函式」每次的「計算結果」
示例
import { Component } from `react`;
import { model, autorun } from `mota`;
import DemoModel from `./models/demo`;
@model(DemoModel)
export default Demo extends Component {
@watch(model=>model.name)
test() {
console.log(`name 發生了變化`);
}
}
上邊的程式碼,通過 watch
裝飾了 test
方法,並指定了觀察的模型資料 model.name
,那麼每當 model.name
發生變化時,都會列印 name 發生了變化
.
watch
是否重新執行,取決於 watch
的作為第一個引數傳給它的「計算函式」的計算結果,每當依賴的模型資料發生變化時 watch
都會重執行計算函式,當計算結果有變化時,才會執行被裝飾的「元件方法」,示例
export default Demo extends Component {
@watch(model=>model.name+model.age)
test() {
console.log(`name 發生變化`);
}
}
有時,我們希望 watch
能首先自動執行一次,那麼可通過向第二個引數傳一個 true
宣告這個 watch
要自動執行一次。
export default Demo extends Component {
@watch(model=>model.name,true)
test() {
console.log(`name 發生變化`);
}
}
上邊的 test
方法,將會在「元件掛載之後自動執行」,之後在 model.name
發生變化時也將自動重新執行。
資料繫結
基本用法
不要驚詫,就是「雙向繫結」。mota
主張「物件導向」,同樣也不排斥「雙向繫結」,使用 mota 能夠實現類似 ng
或 vue
的繫結效果。還是前邊小節中的模型,我們來稍微改動一下元件的程式碼
import { model,binding } from `mota`;
import React from `react`;
import ReactDOM from `react-dom`;
import User from `./models/user`;
@model(User)
@binding
class App extends React.Component {
render(){
const { fullName, firstName, popup } = this.model;
return <div>
<p>{fullName}</p>
<p>
<input data-bind="firstName"/>
<button onClick={popup}> click me </button>
</p>
</div>;
}
}
ReactDOM.render(<App/>, mountNode);
其中的「關鍵」就是 @binding
,使用 @binding
後,元件便具備了「雙向繫結」的能力,在 jsx
中便可以通過名為 data-bind
的自定義 attribute
進行繫結了,data-bind
的值是一個「繫結表示式字串」,繫結表示式執行的 scope
是 model
而不是 this
,也就是隻能與 模型的成員
進行繫結。
會有一種情況是當要繫結的資料是一個迴圈變數時,「繫結表示式」寫起會較麻煩也稍顯長,比如
@model(userModel)
@binding
class App extends React.Component {
render(){
const { userList } = this.model;
return <ul>
{userList.map((user,index)=>(
<li key={user.id}>
<input type="checkobx" data-bind={`userList[${index}].selected`}>
{user.name}
</li>
))}
</ul>;
}
}
因為「繫結表示式」的執行 scope
預設是 this.model
,以及「表示式是個字串」,看一下 userList[${index}].selected
這並不友好,為此 mota 還提供了一個名為 data-scope
的 attribute
,通過它能改變要繫結的 scope
,參考如下示例
@model(userModel)
@binding
class App extends React.Component {
render(){
const { userList } = this.model;
return <ul>
{userList.map(user=>(
<li key={user.id}>
<input type="checkobx" data-scope={user} data-bind="selected">
{user.name}
</li>
))}
</ul>;
}
}
通過 data-scope
將 input
的繫結上下文物件宣告為當前迴圈變數 user
,這樣就可以用 data-bind
直接繫結到對應 user
的屬性上了。
原生表單控制元件
所有的原生表單控制元件,比如「普通 input、checkbox、radio、textarea、select」都可以直接進行繫結。其中,「普通 input 和 textrea」比較簡單,將一個字元型別的模型資料與控制元件繫結就行了,而對於「checkbox 和 radio」 有多種不同的繫結形式。
將「checkbox 或 radio」繫結到一個 boolean
值,此時會將 checkbox 或 radio 的 checked 屬性和模型資料建立繫結,checked 反應了 boolean
變數的值,參考如下示例
@model({ selected:false })
@binding
class App extends React.Component {
render(){
return <div>
<input type="checkbox" data-bind="selected"/>
<input type="radio" data-bind="selected"/>
</div>;
}
}
如上示例通過 this.model.selected
就能拿到當前 checkbox 或 radio 的選中狀態。
將 checkbox 繫結到一個「陣列」,通常是多個 checkbox 繫結同一個陣列變數上,此時和資料建立繫結的是 checkbox 的 value,資料中會包含當前選中的 checkbox 的 value,如下
@model({ selected:[] })
@binding
class App extends React.Component {
render(){
return <div>
<input type="checkbox" data-bind="selected" value="1"/>
<input type="checkbox" data-bind="selected" value="2"/>
</div>;
}
}
如上示例,通過 this.selected
就能知道當前有哪些 checkbox 被選中了,並拿到所有選中的 value
將多個 radio 繫結我到一個「字元型別的變數」,此時和資料建立繫結的是 raido 的 value,因為 radio 是單選的,所以對應的資料是當前選中的 radio 的 value,如下
@model({ selected:`` })
@binding
class App extends React.Component {
render(){
return <div>
<input type="radio" data-bind="selected" value="1"/>
<input type="radio" data-bind="selected" value="2"/>
</div>;
}
}
通過 this.model.selected
就能拿到當前選中的 radio 的 value
自定義元件
但是對於一些「元件庫」中的「部分表單元件」不能直接繫結,因為 mota 並沒有什麼依據可以判斷這是一個什麼元件。所以 mota 提供了一個名為 bindable
的函式,用將任意元件包裝成「可繫結元件」。
bindable 有兩種個引數,用於分別指定「原始元件」和「包裝選項」
//可以這樣
const MyComponent = bindable(opts, Component);
//也可這樣
const MyCompoent = bindable(Component, opts);
關建是 bindable
需要的 opts
,通過 opts
我們可以造訴 mota 如何繫結這個元件,opts
中有兩個重要的成員,它的結構如下
{
value: [`value 對應的屬性名`],
event: [`value 改變的事件名`]
}
所以,我們可以這樣包裝一個自定義文字輸入框
const MyInput = bindable(Input,{
value: [`value`],
event: [`onChange`]
});
對這種「value 不需要轉換,change 能通過 event 或 event.target.value 拿到值」的元件,通過如上的程式碼就能完成包裝了。
對於有 onChange
和 value
的這類文字輸入元件,因為 opts 的預設值就是
{
value: [`value`],
event: [`onChange`]
}
所以,可以更簡單,這樣就行,
const MyInput = bindable(Input);
而對於 checkbox 和 radio 來講,如上邊講到的它「根據不同的資料型有不同的繫結形式」,這就需要指定處理函式了,如下
const radioOpts = {
prop: [`checked`, (ctx, props) => {
const mValue = ctx.getValue();
if (typeof mValue == `boolean`) {
return !!mValue;
} else {
return mValue == props.value;
}
}],
event: [`onChange`, (ctx, event) => {
const { value, checked } = event.target;
const mValue = ctx.getValue();
if (typeof mValue == `boolean`) {
ctx.setValue(checked);
} else if (checked) ctx.setValue(value);
}]
};
通過 prop
的第二個值,能指定「屬性處理函式」,event 的第二個值能指取「事件處理函式」,處理函式的 ctx
是個特殊的物件
-
ctx.getValue
能獲取「當前繫結的模型資料」 -
ctx.setValue
能設定「當前繫結的模型資料」
上邊是 radio
的配置,首先,在「屬性處理函式」中通過繫結的「模型資料的型別」決定 checked
最終的狀態是什麼,並在函式中返回。再次,在「事件處理函式」中通過繫結的「模型資料的型別」決定將什麼值回寫到模型中。
通過「屬性處理函式」和「事件處理函式」幾乎就能將任意的自定義元件轉換為「可繫結元件」了。
另外,對於常見的 CheckBox
和 Radio
型別的元件 mota 也提供了內建的 opts
配置支援,如果一個自定義元件擁有和「原生 checkbox 一致的屬性和事件模型」,那邊可以直接用簡單的方式去包裝,如下
const MyCheckBox = bindable(`checkbox`,CheckBox);
const MyRadio = bindable(`radio`,Radio);
好了,關於繫結就這些了。
文件
連結
相關文章
- 如何寫工程程式碼——重新認識物件導向物件
- [寫作中...]Js物件導向(2): 建立物件JS物件
- 物件導向軟體工程物件軟體工程
- php中的程式導向與物件導向PHP物件
- Golang 中的物件導向Golang物件
- 物件導向-物件導向思想物件
- java物件導向(中)Java物件
- 物件導向的軟體工程方法的優勢物件軟體工程
- 物件導向的編碼設計原則物件
- JS 中的物件導向 prototype classJS物件
- java物件導向作業4Java物件
- java物件導向作業3Java物件
- pickle模組 collections模組在物件導向中的應用物件
- PHP MySQL (三)物件導向 事務PHPMySql物件
- 物件導向與程式導向物件
- 程式導向與物件導向物件
- “程序導向”和“物件導向”物件
- 物件導向物件
- day08 Go中的物件導向Go物件
- PHP中物件導向的分頁類PHP物件
- java的物件導向Java物件
- 物件導向,搞定物件物件
- JAVA物件導向基礎--物件導向介紹Java物件
- PHP 物件導向 (九)物件導向三大特徵PHP物件特徵
- python中物件導向_類_物件的概念與定義Python物件
- day 17 物件導向作業1物件
- 物件業務的追加寫介面物件
- Java中物件導向的設計原則Java物件
- 物件導向中類和物件的定義是什麼?物件
- 更多物件導向的JavaScript物件JavaScript
- JavaScript 的物件導向(OO)JavaScript物件
- Java:Java的物件導向Java物件
- PHP物件導向PHP物件
- 物件導向 -- 反射物件反射
- JavaScript 物件導向JavaScript物件
- JS物件導向JS物件
- Java物件導向Java物件
- Python——物件導向Python物件
- 物件導向--下物件