在 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 开源协议


识别以下二维码,干货


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/522571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

惊呆了!颜值爆表的20+位阿里技术女神同一时间向你发出共事邀请!

摘要&#xff1a; 女神节快到了&#xff0c;云栖社区为此推出“三七”女神节特别分享&#xff0c;20位颜值爆表的阿里女神们同一时间向你发出了共事邀请&#xff0c;快来赴约吧&#xff01; 点此查看原文&#xff1a;http://click.aliyun.com/m/43263/ 女神节快要到了&#xff…

做了中台就不会死吗?每年至少40%开发资源是被浪费的!

戳蓝字“CSDN云计算”关注我们哦&#xff01;文/黄哲铿编辑/Emma上周受邀去一家互联网公司做分享&#xff0c;有学员提到一个问题&#xff1a;“技术中台&#xff0c;如何应对那么多小前台的需求&#xff1f;先做哪个&#xff0c;后做哪个&#xff1f;” 这是个比较普遍问题&am…

bucket sort sample sort 并行_IBM布局AI硬件大杀器:硬软件并行开发、开源模拟AI工具包...

原标题&#xff1a;IBM布局AI硬件大杀器&#xff1a;硬软件并行开发、开源模拟AI工具包智东西(公众号&#xff1a;zhidxcom)编 | 子佩智东西11月4日消息&#xff0c;为了解决AI对数据、能源和内存资源的巨大需求&#xff0c;IBM一直致力于开发节能的AI硬件加速器&#xff0c;希…

如何实现32.5万笔/秒的交易峰值?阿里交易系统TMF2.0技术揭秘

摘要&#xff1a; 交易平台遇到的挑战 2017双11&#xff0c;交易峰值达到了32.5万笔/秒&#xff0c;这给整个交易系统带来了非常大的挑战。 一方面&#xff0c;系统需要支撑全集团几十个事业部的所有交易类需求&#xff1a;要考虑如何能更快响应需求、加快发布周期&#xff1b;…

js统计html页面访问的次数6,JS综合篇--[总结]Web前端常用代码片段整理

IE条件注释条件注释简介IE中的条件注释(Conditional comments)对IE的版本和IE非IE有优秀的区分能力&#xff0c;是WEB设计中常用的hack方法。条件注释只能用于IE5以上&#xff0c;IE10以上不支持。如果你安装了多个IE&#xff0c;条件注释将会以最高版本的IE为标准。条件注释的…

使用TensorFlow,GPU和Docker容器进行深度学习

摘要&#xff1a; 数据科学家使用GPU来提高TensorFlow的计算速度&#xff0c;但GPU价格昂贵&#xff0c;也需要对其所占用的资源进行认真的管理。本文将带你来一起解决这一问题。在过去的几个月中&#xff0c;我和多个企业的数据科学团队进行了多次合作&#xff0c;也看到越来越…

一键部署 Spring Boot 到远程 Docker 容器

首先构建一个简单的 Spring Boot 项目&#xff0c;然后给项目添加 Docker 支持&#xff0c;最后对项目进行部署。 文章目录一、前提条件1. SpringBoot项目2. Docker插件3. Dockerfile文件4. 服务器Docker环境5. Maven环境二、技术选型三、新建SpringBoot项目1. pom2. DockerCon…

Docker,一个傲娇的男人

戳蓝字“CSDN云计算”关注我们哦&#xff01;引言大概几个月前&#xff0c;我曾经写过一篇文章叫《微服务为什么一定要用docker》。当时&#xff0c;写完这篇以后&#xff0c;有些粉丝表示想看看基本入门教程&#xff0c;希望我写一篇。然后呢&#xff0c;大家也知道&#xff0…

英特尔核芯显卡控制面板没有了_只认性能你就输了!英特尔第十代酷睿处理器最全解析...

前不久&#xff0c;英特尔公布了第十代酷睿处理器“Ice Lake”的命名规则&#xff0c;AnandTech网站也曝光了“次旗舰”级别酷睿i7-1065G7处理器的实测性能(详见《10nm新架构Iris Plus核显 第十代酷睿到底有多强&#xff1f;》)。从结果来看&#xff0c;i7-1065G7的CPU性能提升…

阿里敏捷教练何勉:论精益思想及精益产品开发实践体系

摘要&#xff1a; 精益求精是工匠精神实现的最佳方法&#xff0c;通过引入实践精益思想的原则和方法进行精益产品开发&#xff0c;打造对客户最好的产品进行交付&#xff0c;其次通过精益思想的理念降低企业的运营成本&#xff0c;提高企业的运营效率。阿里资深解决方案架构师、…

html页面内分栏显示不全,怎么消除Word文档分栏后栏间不平衡现象

IE10浏览器打开网页鼠标不能滚动查看是怎么回事&#xff1f;IE10浏览器黑屏&#xff0c;但是滚动鼠标就好了&#xff0c;不到一分钟...在控制面板的电源设置中&#xff0c;将显示器的关闭时间调到你希望的时刻就可以了。word文档分栏后左右对不齐怎么办word文档分栏后左右对不齐…

开发经验分享_06_前端开发技巧

接上一篇&#xff1a;(企业内部)开发经验分享_05_葫芦画瓢 https://gblfy.blog.csdn.net/article/details/103414567 文章目录一、JS调试技巧1. 推荐使用consde.log2. 推荐理由3. Network正确的使用姿势①Headers一、JS调试技巧 1. 推荐使用consde.log 推荐使用consde.log(内…

Spring精华问答 | 为什么要学习Spring?

戳蓝字“CSDN云计算”关注我们哦&#xff01;Spring是为解决企业应用程序开发复杂性而创建的一个Java开源框架&#xff0c;应用非常广泛。业内非常流行的SSH架构中的其中一个"S"指的就是Spring。今天我们就一起来看看关于Spring的精华问答&#xff01;1Q&#xff1a;…

相关系数excel_如何用Excel计算投资组合的有效前沿?

假设一个投资组合由美债和美股构成&#xff0c;美债为跟踪美国投资级债券市场走势的交易所交易基金AGG&#xff0c;美股为跟踪美国标准普尔500指数走势的交易所交易基金SPY。注&#xff1a;在计算有效前沿曲线时投资回报率一般应采用预期回报率&#xff0c;但本文只是为了演示投…

拼的html页面乱,页面分页html拼接

success:function(data){if(data.status "success"){var page data.p;var html;for(var i 0; ihtml html ;htmlhtml;htmlhtmlpage.result[i].infoTitle;htmlhtmlpage.result[i].infoDepict ;htmlhtml;}// 添加分页选项:下一页html html "" "&q…

LoRaWAN开放式实验平台

摘要&#xff1a; 本文介绍了基于loraserver和uDC的LoRaWAN开发平台&#xff0c;基于该平台用户无需投入硬件即可在设备端和服务端进行&#xff0c;极大的降低了入门和开发门槛。 点此查看原文&#xff1a;http://click.aliyun.com/m/43348/ 本文旨在介绍AliOS Things的LoRaWA…

华为开发者大会上,鸿蒙问世、方舟编译器开源、还有 EMUI 10;壕置100万美元,苹果推出漏洞攻击报告赏金计划……...

关注并标星星CSDN云计算极客头条&#xff1a;速递、最新、绝对有料。这里有企业新动、这里有业界要闻&#xff0c;打起十二分精神&#xff0c;紧跟fashion你可以的&#xff01;每周三次&#xff0c;打卡即read更快、更全了解泛云圈精彩newsgo go go 鸿蒙OS正式对外发布&#xf…

bootstarp怎么使盒子到最右边_折纸教程:漂亮花朵盒子图解,简单实用,一张纸就完成...

爱生活&#xff0c;爱手工&#xff0c;我是爱做手工的小曦&#xff01;今天给大家带来的是颜值很高的花朵盒子折纸&#xff0c;可以放一些小物件哦&#xff01;比如棉签什么的&#xff0c;还是很实用的。具体怎么折&#xff0c;和小曦一起来看看吧&#xff01;花朵盒子折纸教程…

开放分布式追踪(OpenTracing)入门与 Jaeger 实现

摘要&#xff1a; 分布式系统的运维挑战 容器、Serverless 编程方式的诞生极大提升了软件交付与部署的效率。在架构的演化过程中&#xff0c;可以看到两个变化&#xff1a; 应用架构开始从单体系统逐步转变为微服务&#xff0c;其中的业务逻辑随之而来就会变成微服务之间的调用…