React 是一個「視圖層」的 UI 框架,以常見的 MVC 來說 React 僅是 View,而咱們在編寫應用時,一般還須要關注更加劇要的 model,對於 React 來說,咱們經常須要一個「狀態管理」庫。然而,目前大多數針對 React 的狀態管理庫都是「強依賴」過多的侵入本應該獨立的業務模型中,致使「業務邏輯」對應的代碼並不能輕易在其它地方重用,每每這些框架還具備「強排它性」,可是「業務模型」應該是沒有過多依賴,應該是無關框架的,它應該隨時能夠被用在任何合適的 JavaScript 環境中,使用 mota 你能夠用原生的普通的 JavaScript 代碼編寫你的「業務模型」,並讓你的「業務模型」在不一樣框架、不一樣運行環境下重用更爲容易。html
mota 是一個主張「面向對象」的、支持「雙向綁定」的 React 應用輔助庫,基於 mota 你能夠用純 JavaScript 爲應用編寫徹底面向對象的「業務模型」,並輕易的將「業務模型」關聯到 React 應用中。vue
在線 TodoList 示例 (示例源碼)react
經過 npm 安裝,以下git
$ npm i mota --save
複製代碼
或經過 dawn
腳手腳加建立工程,以下github
$ mkdir your_path
$ cd your_path
$ dn init -t mota
$ dn dev
複製代碼
須要先安裝 dawn(Dawn 安裝及使用文檔)npm
一個 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 固有的面向對象編程的知識。json
以下示例經過編寫一個名爲 User
的 class
建立了一個「用戶模型」數組
export default class User {
firstName = 'Jack';
lastName = 'Hou';
get fullName(){
reutrn `${this.firstName} ${this.lastName}`;
}
}
複製代碼
也能夠是一個 Object
,一般這個模型須要是「單例」時,可採用這種方式,以下app
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);
複製代碼
好了,關於綁定就這些了。