内容简介:你可以通过 omi-cli 快速体验 MVVM:
写在前面
腾讯 Omi 框架 正式发布 5.0,依然专注于 View,但是对 MVVM 架构更加友好的集成,彻底分离视图与业务逻辑的架构。
你可以通过 omi-cli 快速体验 MVVM:
$ npm i omi-cli -g $ omi init-mvvm my-app $ cd my-app $ npm start $ npm run build 复制代码
npx omi-cli init-mvvm my-app
也支持(要求 npm v5.2.0+)
MVVM 演化
MVVM 其实本质是由 MVC、MVP 演化而来。
目的都是分离视图和模型,但是在 MVC 中,视图依赖模型,耦合度太高,导致视图的可移植性大大降低,在 MVP 模式中,视图不直接依赖模型,由 P(Presenter)负责完成 Model 和 View 的交互。MVVM 和 MVP 的模式比较接近。ViewModel 担任这 Presenter 的角色,并且提供 UI 视图所需要的数据源,而不是直接让 View 使用 Model 的数据源,这样大大提高了 View 和 Model 的可移植性,比如同样的 Model 切换使用 Flash、HTML、WPF 渲染,比如同样 View 使用不同的 Model,只要 Model 和 ViewModel 映射好,View 可以改动很小甚至不用改变。
Mappingjs
当然 MVVM 这里会出现一个问题, Model 里的数据映射到 ViewModel 提供该视图绑定,怎么映射?手动映射?自动映射?在ASP.NET MVC 中,有强大的AutoMapper 用来映射。针对 JS 环境,我特地封装了 mappingjs 用来映射 Model 到 ViewModel。
const testObj = { same: 10, bleh: 4, firstName: 'dnt', lastName: 'zhang', a: { c: 10 } } const vmData = mapping({ from: testObj, to: { aa: 1 }, rule: { dumb: 12, func: function () { return 8 }, b: function () { //可递归映射 return mapping({ from: this.a }) }, bar: function () { return this.bleh }, //可以重组属性 fullName: function () { return this.firstName + this.lastName }, //可以映射到 path 'd[2].b[0]': function () { return this.a.c } } }) 复制代码
你可以通后 npm 安装使用:
npm i mappingjs 复制代码
再举例说明:
var a = { a: 1 } var b = { b: 2 } assert.deepEqual(mapping({ from: a, to: b }), { a: 1, b: 2 }) 复制代码
Deep mapping:
QUnit.test("", function (assert) { var A = { a: [{ name: 'abc', age: 18 }, { name: 'efg', age: 20 }], e: 'aaa' } var B = mapping({ from: A, to: { d: 'test' }, rule: { a: null, c: 13, list: function () { return this.a.map(function (item) { return mapping({ from: item }) }) } } }) assert.deepEqual(B.a, null) assert.deepEqual(B.list[0], A.a[0]) assert.deepEqual(B.c, 13) assert.deepEqual(B.d, 'test') assert.deepEqual(B.e, 'aaa') assert.deepEqual(B.list[0] === A.a[0], false) }) 复制代码
Deep deep mapping:
QUnit.test("", function (assert) { var A = { a: [{ name: 'abc', age: 18, obj: { f: 'a', l: 'b' } }, { name: 'efg', age: 20, obj: { f: 'a', l: 'b' } }], e: 'aaa' } var B = mapping({ from: A, rule: { list: function () { return this.a.map(function (item) { return mapping({ from: item, rule: { obj: function () { return mapping({ from: this.obj }) } } }) }) } } }) assert.deepEqual(A.a, B.list) assert.deepEqual(A.a[0].obj, B.list[0].obj) assert.deepEqual(A.a[0].obj === B.list[0].obj, false) }) 复制代码
Omi MVVM Todo 实战
定义 Model:
let id = 0 export default class TodoItem { constructor(text, completed) { this.id = id++ this.text = text this.completed = completed || false this.author = { firstName: 'dnt', lastName: 'zhang' } } clone() { return new TodoItem(this.text, this.completed) } } 复制代码
Todo 就省略不贴出来了,太长了,可以直接 看这里 。反正统一按照面向对象程序设计进行抽象和封装。
定义 ViewModel:
import mapping from 'mappingjs' import shared from './shared' import todoModel from '../model/todo' import ovm from './other' class TodoViewModel { constructor() { this.data = { items: [] } } update(todo) { //这里进行映射 todo && todo.items.forEach((item, index) => { this.data.items[index] = mapping({ from: item, to: this.data.items[index], rule: { fullName: function() { return this.author.firstName + this.author.lastName } } }) }) this.data.projName = shared.projName } add(text) { todoModel.add(text) this.update(todoModel) ovm.update() this.update() } getAll() { todoModel.getAll(() => { this.update(todoModel) ovm.update() this.update() }) } changeSharedData() { shared.projName = 'I love omi-mvvm.' ovm.update() this.update() } } const vd = new TodoViewModel() export default vd 复制代码
- vm 只专注于 update 数据,视图会自动更新
- 公共的数据或 vm 可通过 import 依赖
定义 View, 注意下面是继承自 ModelView 而非 WeElement。
import { ModelView, define } from 'omi' import vm from '../view-model/todo' import './todo-list' import './other-view' define('todo-app', class extends ModelView { vm = vm onClick = () => { //view model 发送指令 vm.changeSharedData() } install() { //view model 发送指令 vm.getAll() } render(props, data) { return ( <div> <h3>TODO</h3> <todo-list items={data.items} /> <form onSubmit={this.handleSubmit}> <input onChange={this.handleChange} value={this.text} /> <button>Add #{data.items.length + 1}</button> </form> <div>{data.projName}</div> <button onClick={this.onClick}>Change Shared Data</button> <other-view /> </div> ) } handleChange = e => { this.text = e.target.value } handleSubmit = e => { e.preventDefault() if(this.text !== ''){ //view model 发送指令 vm.add(this.text) this.text = '' } } }) 复制代码
- 所有数据通过 vm 注入
- 所以指令通过 vm 发出
小结
从宏观的角度来看,Omi 的 MVVM 架构也属性网状架构,网状架构目前来看有:
- Mobx + React
- Hooks + React
- MVVM (Omi)
大势所趋!简直是前端工程化最佳实践!也可以理解成网状结构是描述和抽象世界的最佳途径。那么网在哪?
- ViewModel 与 ViewModel 之间相互依赖甚至循环依赖的网状结构
- ViewModel 一对一、多对一、一对多、多对多依赖 Models 形成网状结构
- Model 与 Model 之间形成相互依赖甚至循环依赖的网状结构
- View 一对一依赖 ViewModel 形成网状结构
总结如下:
Model | ViewModel | View | |
---|---|---|---|
Model | 多对多 | 多对多 | 无关联 |
ViewModel | 多对多 | 多对多 | 一对一 |
View | 无关联 | 一多一 | 多对多 |
其余新增特性
单位 rpx 的支持
import { render, WeElement, define, rpx } from 'omi' define('my-ele', class extends WeElement { css() { return rpx(`div { font-size: 375rpx }`) } render() { return ( <div>abc</div> ) } }) render(<my-ele />, 'body') 复制代码
比如上面定义了半屏幕宽度的 div。
htm 支持
htm 是谷歌工程师,preact作者最近的作品,不管它是不是未来,先支持了再说:
import { define, render, WeElement } from 'omi' import 'omi-html' define('my-counter', class extends WeElement { static observe = true data = { count: 1 } sub = () => { this.data.count-- } add = () => { this.data.count++ } render() { return html` <div> <button onClick=${this.sub}>-</button> <span>${this.data.count}</span> <button onClick=${this.add}>+</button> </div>` } }) render(html`<my-counter />`, 'body') 复制代码
你甚至可以直接使用下面代码在现代浏览器中运行,不需要任何构建工具:
Hooks 类似的 API
你也可以定义成纯函数的形式:
import { define, render } from 'omi' define('my-counter', function() { const [count, setCount] = this.use({ data: 0, effect: function() { document.title = `The num is ${this.data}.` } }) this.useCss(`button{ color: red; }`) return ( <div> <button onClick={() => setCount(count - 1)}>-</button> <span>{count}</span> <button onClick={() => setCount(count + 1)}>+</button> </div> ) }) render(<my-counter />, 'body') 复制代码
如果你不需要 effect 方法, 可以直接使用 useData
:
const [count, setCount] = this.useData(0) 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 王者编程大赛之三 — 01背包
- 深度学习新王者:AutoML和NAS
- Redux从青铜到王者—概念篇(一)
- 面试中 LRU / LFU 的青铜与王者
- Python爬取王者荣耀英雄皮肤高清图片
- 把英雄分类,看 Python 带你上王者
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Domain-Driven Design Distilled
Vaughn Vernon / Addison-Wesley Professional / 2016-6-2 / USD 36.99
Domain-Driven Design (DDD) software modeling delivers powerful results in practice, not just in theory, which is why developers worldwide are rapidly moving to adopt it. Now, for the first time, there......一起来看看 《Domain-Driven Design Distilled》 这本书的介绍吧!