在 React 工程中利用 Mota 编写面向对象的业务模型
摘要:?## 簡述 React 是一個「視圖層」的 UI 框架,以常見的 MVC 來講 React 僅是 View,而我們在編寫應用時,通常還需要關注更加重要的 model,對于 React 來講,我們常常需要一個「狀態管理」庫。然而,目前大多數針對 React 的狀態管理庫都是「強依賴」過多的侵入本應該獨立的業務模型中,導致「業務邏輯」對應的代碼并不能輕易在其它地方重用,往往這些框架還具有「強排它
原文:https://zhuanlan.zhihu.com/p/33778168
簡述
React 是一個「視圖層」的 UI 框架,以常見的 MVC 來講 React 僅是 View,而我們在編寫應用時,通常還需要關注更加重要的 model,對于 React 來講,我們常常需要一個「狀態管理」庫。然而,目前大多數針對 React 的狀態管理庫都是「強依賴」過多的侵入本應該獨立的業務模型中,導致「業務邏輯」對應的代碼并不能輕易在其它地方重用,往往這些框架還具有「強排它性」,但是「業務模型」應該是沒有過多依賴,應該是無關框架的,它應該隨時可以被用在任何合適的 JavaScript 環境中,使用 mota 你可以用原生的普通的 JavaScript 代碼編寫你的「業務模型」,并讓你的「業務模型」在不同框架、不同運行環境下重用更為容易。
mota 是一個主張「面向對象」的、支持「雙向綁定」的 React 應用輔助庫,基于 mota 你可以用純 JavaScript 為應用編寫完全面向對象的「業務模型」,并輕易的將「業務模型」關聯到 React 應用中。
示例
在線 TodoList 示例
(示例源碼)
安裝
通過 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 {@autoruntest() {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);好了,關于綁定就這些了。
文檔
- 快速開始
- 編寫業務模型
- 將組件屬性映射到模型
- 自執行函數
- 監聽模型變化
- 將模型數據與表單綁定
鏈接
- 開源地址
- 版本發布日志
- MIT 開源協議
識別以下二維碼,干貨
總結
以上是生活随笔為你收集整理的在 React 工程中利用 Mota 编写面向对象的业务模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用拓扑数据分析理解卷积神经网络模型的工
- 下一篇: 如何完成一次Apache的版本发布