在 React 工程中利用 Mota 编写面向对象的业务模型
简述React 是一个「视图层」的 UI 框架,以常见的 MVC 来讲 React 仅是 View,而我们在编写应用时,通常还需要关注更加重要的 model,对于 React 来讲,我们常常需要一个「状态管理」库。然而,目前大多数针对 React 的状态管理库都是「强依赖」过多的侵入本应该独立的业务模型中,导致「业务逻辑」对应的代码并不能轻易在其它地方重用,往往这些框架还具有「强排它性」,但是「业务模型」应该是没有过多依赖,应该是无关框架的,它应该随时可以被用在任何合适的 JavaScript 环境中,使用 mota 你可以用原生的普通的 JavaScript 代码编写你的「业务模型」,并让你的「业务模型」在不同框架、不同运行环境下重用更为容易。 mota 是一个主张「面向对象」的、支持「双向绑定」的 React 应用辅助库,基于 mota 你可以用纯 JavaScript 为应用编写完全面向对象的「业务模型」,并轻易的将「业务模型」关联到 React 应用中。 链接
示例在线 TodoList 示例 安装通过 npm 安装,如下 $ npm i mota --save 或通过 $ mkdir your_path $ cd your_path $ dn init -t mota $ dn dev 需要先安装 dawn(Dawn 安装及使用文档) 工程结构一个 . ├── 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 编写业务模型在你编写模型之前,先放下 React 也放下 mota,就用单纯的 JavaScript 去编写你的业务模型,或有一个或多个类、或就是几个 Object,依它们应有的、自然的关系去抽像就行了,业务模型不依赖于 UI、也不依赖于某个框架,它易于测试,你可以针对它做单元测试。它易于重用,你可以将它用在合适的地方。最后, mota 只是出场把它关联到 react。 在 mota 中「模型」可以是由一个 如下示例通过编写一个名为 export default class User { firstName = 'Jack'; lastName = 'Hou'; get fullName(){ reutrn `${this.firstName} ${this.lastName}`; } } 也可以是一个 export default { firstName: 'Jack',lastName: 'Hou',get fullName(){ reutrn `${this.firstName} ${this.lastName}`; } }; 在「业务模型」编写完成后,可以通过 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); 值得注意的是,在使用 属性映射在 React 中通常会将应用折分为多个组件重用它们,并在用时传递给它「属性」,mota 提供了将「组件属性」映射到「模型数据」的能力,基于 @model({ value: 'demo' }) @mapping(['value']) class Demo extends React.Component { render () { return <div>{this.model.value}</div>; } } 上边的代码通过 通过一个 map 进行映射,还可以让「组件属性」和「模型的成员」使用不同名称,如下: @model({ value: 'demo' }) @mapping({ content: 'value' }) class Demo extends React.Component { render () { return <div>{this.model.value}</div>; } } 上边的代码,将组件 demo 的 自执行函数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); } } 上边的示例代码中,组件在被挂载后将会自动执行 监听模型变化mota 中提供了一个
示例 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 发生了变化'); } } 上边的代码,通过
export default Demo extends Component { @watch(model=>model.name+model.age) test() { console.log('name 发生变化'); } } 有时,我们希望 export default Demo extends Component { @watch(model=>model.name,true) test() { console.log('name 发生变化'); } } 上边的 数据绑定基本用法不要惊诧,就是「双向绑定」。 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); 其中的「关键」就是 会有一种情况是当要绑定的数据是一个循环变量时,「绑定表达式」写起会较麻烦也稍显长,比如 @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>; } } 因为「绑定表达式」的执行 @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>; } } 通过 原生表单控件所有的原生表单控件,比如「普通 input、checkbox、radio、textarea、select」都可以直接进行绑定。其中,「普通 input 和 textrea」比较简单,将一个字符类型的模型数据与控件绑定就行了,而对于「checkbox 和 radio」 有多种不同的绑定形式。 将「checkbox 或 radio」绑定到一个 @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>; } } 如上示例通过 将 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>; } } 如上示例,通过 将多个 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>; } } 通过 自定义组件但是对于一些「组件库」中的「部分表单组件」不能直接绑定,因为 mota 并没有什么依据可以判断这是一个什么组件。所以 mota 提供了一个名为 bindable 有两种个参数,用于分别指定「原始组件」和「包装选项」 //可以这样 const MyComponent = bindable(opts,Component); //也可这样 const MyCompoent = bindable(Component,opts); 关建是 { value: ['value 对应的属性名'],event: ['value 改变的事件名'] } 所以,我们可以这样包装一个自定义文本输入框 const MyInput = bindable(Input,{ value: ['value'],event: ['onChange'] }); 对这种「value 不需要转换,change 能通过 event 或 event.target.value 拿到值」的组件,通过如上的代码就能完成包装了。 对于有 { 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',event) => { const { value,checked } = event.target; const mValue = ctx.getValue(); if (typeof mValue == 'boolean') { ctx.setValue(checked); } else if (checked) ctx.setValue(value); }] }; 通过
上边是 通过「属性处理函数」和「事件处理函数」几乎就能将任意的自定义组件转换为「可绑定组件」了。 另外,对于常见的 const MyCheckBox = bindable('checkbox',CheckBox); const MyRadio = bindable('radio',Radio); 好了,关于绑定就这些了。 文档
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |