内容简介:你可以通过 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 带你上王者
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing Web Navigation
James Kalbach / O'Reilly Media / 2007-8-15 / USD 49.99
Thoroughly rewritten for today's web environment, this bestselling book offers a fresh look at a fundamental topic of web site development: navigation design. Amid all the changes to the Web in the pa......一起来看看 《Designing Web Navigation》 这本书的介绍吧!