写在前面

腾讯 Omi 框架正式发布 5.0,依然专注于 View,但是对 MVVM 架构更加友好的集成,彻底分离视图与业务逻辑的架构。

你可以通过 omi-cli 快速体验 MVVM:

  1. $ npm i omi-cli -g
  2. $ omi init-mvvm my-app
  3. $ cd my-app
  4. $ npm start
  5. $ 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。

  1. const testObj = {
  2. same: 10,
  3. bleh: 4,
  4. firstName: 'dnt',
  5. lastName: 'zhang',
  6. a: {
  7. c: 10
  8. }
  9. }
  10. const vmData = mapping({
  11. from: testObj,
  12. to: { aa: 1 },
  13. rule: {
  14. dumb: 12,
  15. func: function () {
  16. return 8
  17. },
  18. b: function () {
  19. //可递归映射
  20. return mapping({ from: this.a })
  21. },
  22. bar: function () {
  23. return this.bleh
  24. },
  25. //可以重组属性
  26. fullName: function () {
  27. return this.firstName + this.lastName
  28. },
  29. //可以映射到 path
  30. 'd[2].b[0]': function () {
  31. return this.a.c
  32. }
  33. }
  34. })

你可以通后 npm 安装使用:

  1. npm i mappingjs

再举例说明:

  1. var a = { a: 1 }
  2. var b = { b: 2 }
  3. assert.deepEqual(mapping({
  4. from: a,
  5. to: b
  6. }), { a: 1, b: 2 })

Deep mapping:


  1. QUnit.test("", function (assert) {
  2. var A = { a: [{ name: 'abc', age: 18 }, { name: 'efg', age: 20 }], e: 'aaa' }
  3. var B = mapping({
  4. from: A,
  5. to: { d: 'test' },
  6. rule: {
  7. a: null,
  8. c: 13,
  9. list: function () {
  10. return this.a.map(function (item) {
  11. return mapping({ from: item })
  12. })
  13. }
  14. }
  15. })
  16. assert.deepEqual(B.a, null)
  17. assert.deepEqual(B.list[0], A.a[0])
  18. assert.deepEqual(B.c, 13)
  19. assert.deepEqual(B.d, 'test')
  20. assert.deepEqual(B.e, 'aaa')
  21. assert.deepEqual(B.list[0] === A.a[0], false)
  22. })

Deep deep mapping:


  1. QUnit.test("", function (assert) {
  2. var A = { a: [{ name: 'abc', age: 18, obj: { f: 'a', l: 'b' } }, { name: 'efg', age: 20, obj: { f: 'a', l: 'b' } }], e: 'aaa' }
  3. var B = mapping({
  4. from: A,
  5. rule: {
  6. list: function () {
  7. return this.a.map(function (item) {
  8. return mapping({
  9. from: item, rule: {
  10. obj: function () {
  11. return mapping({ from: this.obj })
  12. }
  13. }
  14. })
  15. })
  16. }
  17. }
  18. })
  19. assert.deepEqual(A.a, B.list)
  20. assert.deepEqual(A.a[0].obj, B.list[0].obj)
  21. assert.deepEqual(A.a[0].obj === B.list[0].obj, false)
  22. })

Omi MVVM Todo 实战

定义 Model:

  1. let id = 0
  2. export default class TodoItem {
  3. constructor(text, completed) {
  4. this.id = id++
  5. this.text = text
  6. this.completed = completed || false
  7. this.author = {
  8. firstName: 'dnt',
  9. lastName: 'zhang'
  10. }
  11. }
  12. clone() {
  13. return new TodoItem(this.text, this.completed)
  14. }
  15. }

Todo 就省略不贴出来了,太长了,可以直接 看这里。反正统一按照面向对象程序设计进行抽象和封装。

定义 ViewModel:

  1. import mapping from 'mappingjs'
  2. import shared from './shared'
  3. import todoModel from '../model/todo'
  4. import ovm from './other'
  5. class TodoViewModel {
  6. constructor() {
  7. this.data = {
  8. items: []
  9. }
  10. }
  11. update(todo) {
  12. //这里进行映射
  13. todo &&
  14. todo.items.forEach((item, index) => {
  15. this.data.items[index] = mapping({
  16. from: item,
  17. to: this.data.items[index],
  18. rule: {
  19. fullName: function() {
  20. return this.author.firstName + this.author.lastName
  21. }
  22. }
  23. })
  24. })
  25. this.data.projName = shared.projName
  26. }
  27. add(text) {
  28. todoModel.add(text)
  29. this.update(todoModel)
  30. ovm.update()
  31. }
  32. getAll() {
  33. todoModel.getAll(() => {
  34. this.update(todoModel)
  35. ovm.update())
  36. })
  37. }
  38. changeSharedData() {
  39. shared.projName = 'I love omi-mvvm.'
  40. ovm.update()
  41. this.update()
  42. }
  43. }
  44. const vd = new TodoViewModel()
  45. export default vd
  • vm 只专注于 update 数据,视图会自动更新
  • 公共的数据或 vm 可通过 import 依赖

定义 View, 注意下面是继承自 ModelView 而非 WeElement。

  1. import { ModelView, define } from 'omi'
  2. import vm from '../view-model/todo'
  3. import './todo-list'
  4. import './other-view'
  5. define('todo-app', class extends ModelView {
  6. vm = vm
  7. onClick = () => {
  8. //view model 发送指令
  9. vm.changeSharedData()
  10. }
  11. install() {
  12. //view model 发送指令
  13. vm.getAll()
  14. }
  15. render(props, data) {
  16. return (
  17. <div>
  18. <h3>TODO</h3>
  19. <todo-list items={data.items} />
  20. <form onSubmit={this.handleSubmit}>
  21. <input onChange={this.handleChange} value={this.text} />
  22. <button>Add #{data.items.length + 1}</button>
  23. </form>
  24. <div>{data.projName}</div>
  25. <button onClick={this.onClick}>Change Shared Data</button>
  26. <other-view />
  27. </div>
  28. )
  29. }
  30. handleChange = e => {
  31. this.text = e.target.value
  32. }
  33. handleSubmit = e => {
  34. e.preventDefault()
  35. if(this.text !== ''){
  36. //view model 发送指令
  37. vm.add(this.text)
  38. this.text = ''
  39. }
  40. }
  41. })
  • 所有数据通过 vm 注入
  • 所以指令通过 vm 发出
  1. define('todo-list', function(props) {
  2. return (
  3. <ul>
  4. {props.items.map(item => (
  5. <li key={item.id}>
  6. {item.text} <span>by {item.fullName}</span>
  7. </li>
  8. ))}
  9. </ul>
  10. )
  11. })

可以看到 todo-list 可以直接使用 fullName

→ 完整代码戳这里

mapping.auto

是不是感觉映射写起来略微麻烦?? 简单的还好,复杂对象嵌套很深就会很费劲。没关系 mapping.auto 拯救你!

  • mapping.auto(from, [to]) 其中 to 是可选参数

举个例子:

  1. class TodoItem {
  2. constructor(text, completed) {
  3. this.text = text
  4. this.completed = completed || false
  5. this.author = {
  6. firstName: 'dnt',
  7. lastName: 'zhang'
  8. }
  9. }
  10. }
  11. const res = mapping.auto(new TodoItem('task'))
  12. deepEqual(res, {
  13. author: {
  14. firstName: "dnt",
  15. lastName: "zhang"
  16. },
  17. completed: false,
  18. text: "task"
  19. })

你可以把任意 class 映射到简单的 json obj!那么开始改造 ViewModel:

  1. class TodoViewModel {
  2. constructor() {
  3. this.data = {
  4. items: []
  5. }
  6. }
  7. update(todo) {
  8. todo && mapping.auto(todo, this.data)
  9. this.data.projName = shared.projName
  10. }
  11. ...
  12. ...
  13. ...

以前的一堆映射逻辑变成了一行代码: mapping.auto(todo, this.data)。当然由于没有 fullName 属性了,这里需要在视图里直接使用映射过来的 author:

  1. define('todo-list', function(props) {
  2. return (
  3. <ul>
  4. {props.items.map(item => (
  5. <li key={item.id}>
  6. {item.text} <span>by {item.author.firstName + item.author.lastName}</span>
  7. </li>
  8. ))}
  9. </ul>
  10. )
  11. })

小结

从宏观的角度来看,Omi 的 MVVM 架构也属性网状架构,网状架构目前来看有:

  • Mobx + React
  • Hooks + React
  • MVVM (Omi)

大势所趋!简直是前端工程化最佳实践!也可以理解成网状结构是描述和抽象世界的最佳途径。那么网在哪?

  • ViewModel 与 ViewModel 之间相互依赖甚至循环依赖的网状结构
  • ViewModel 一对一、多对一、一对多、多对多依赖 Models 形成网状结构
  • Model 与 Model 之间形成相互依赖甚至循环依赖的网状结构
  • View 一对一依赖 ViewModel 形成网状结构

总结如下:

Model ViewModel View
Model 多对多 多对多 无关联
ViewModel 多对多 多对多 一对一
View 无关联 一多一 多对多

其余新增特性

单位 rpx 的支持

  1. import { render, WeElement, define, rpx } from 'omi'
  2. define('my-ele', class extends WeElement {
  3. css() {
  4. return rpx(`div { font-size: 375rpx }`)
  5. }
  6. render() {
  7. return (
  8. <div>abc</div>
  9. )
  10. }
  11. })
  12. render(<my-ele />, 'body')

比如上面定义了半屏幕宽度的 div。

htm 支持

htm 是谷歌工程师,preact作者最近的作品,不管它是不是未来,先支持了再说:

  1. import { define, render, WeElement } from 'omi'
  2. import 'omi-html'
  3. define('my-counter', class extends WeElement {
  4. static observe = true
  5. data = {
  6. count: 1
  7. }
  8. sub = () => {
  9. this.data.count--
  10. }
  11. add = () => {
  12. this.data.count++
  13. }
  14. render() {
  15. return html`
  16. <div>
  17. <button onClick=${this.sub}>-</button>
  18. <span>${this.data.count}</span>
  19. <button onClick=${this.add}>+</button>
  20. </div>`
  21. }
  22. })
  23. render(html`<my-counter />`, 'body')

你甚至可以直接使用下面代码在现代浏览器中运行,不需要任何构建工具:

Hooks 类似的 API

你也可以定义成纯函数的形式:

  1. import { define, render } from 'omi'
  2. define('my-counter', function() {
  3. const [count, setCount] = this.use({
  4. data: 0,
  5. effect: function() {
  6. document.title = `The num is ${this.data}.`
  7. }
  8. })
  9. this.useCss(`button{ color: red; }`)
  10. return (
  11. <div>
  12. <button onClick={() => setCount(count - 1)}>-</button>
  13. <span>{count}</span>
  14. <button onClick={() => setCount(count + 1)}>+</button>
  15. </div>
  16. )
  17. })
  18. render(<my-counter />, 'body')

如果你不需要 effect 方法, 可以直接使用 useData:

  1. const [count, setCount] = this.useData(0)

更多的模板选择

Template Type Command Describe
Base Template omi init my-app 基础模板
TypeScript Template(omi-cli v3.0.5+) omi init-ts my-app 使用 TypeScript 的模板
SPA Template(omi-cli v3.0.10+) omi init-spa my-app 使用 omi-router 单页应用的模板
omi-mp Template(omi-cli v3.0.13+) omi init-mp my-app 小程序开发 Web 的模板
MVVM Template(omi-cli v3.0.22+) omi init-mvvm my-app MVVM 模板

Star & Fork

腾讯 Omi 5.0 发布 - Web 前端 MVVM 王者归来,mappingjs 强力加持的更多相关文章

  1. IIS7.0发布Web服务器0002

    asp.net发布到IIS中出现错误:处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler” 分类: BS学 ...

  2. 腾讯 AlloyCrop 1.0 发布

    写在前面 AlloyCrop 这个项目是8个月前发布的,作为AlloyFinger 的典型案例,发布之后被BAT等其他公司广泛使用.但是发布之后,有两个问题一直没有抽出时间去解决: 裁剪图像的分辨率太 ...

  3. IIS7.0发布Web服务-0001

    配置错误 不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的 (overrideModeDefault="Deny"),或者是通过包含 ove ...

  4. Eclipse如何发布web项目

    目录结构: // contents structure [-] 需要的环境 下载和配置JDK 下载和配置Tomcat 下载Eclipse Eclipse 4.4.0 发布Web步骤 创建server ...

  5. 腾讯发布新版前端组件框架 Omi,全面拥抱 Web Components

    Omi - 合一 下一代 Web 框架,去万物糟粕,合精华为一 → https://github.com/Tencent/omi 特性 4KB 的代码尺寸,比小更小 顺势而为,顺从浏览器的发展和 AP ...

  6. Omi v1.0震撼发布 - 令人窒息的Web组件化框架

    原文链接--https://github.com/AlloyTeam/omi 写在前面 Omi框架经过几十个版本的迭代,越来越简便易用和强大. 经过周末的连续通宵加班加点,Omi v1.0版本终于问世 ...

  7. Omi v1.0震撼发布 - 开放现代的Web组件化框架

    原文链接--https://github.com/AlloyTeam/omi 写在前面 Omi框架经过几十个版本的迭代,越来越简便易用和强大. 经过周末的连续通宵加班加点,Omi v1.0版本终于问世 ...

  8. 2015腾讯web前端笔试题

      1 请实现,鼠标点击页面中的任意标签,alert该标签的名称.(注意兼容性) 2 请指出一下代码的性能问题,并经行优化. var info="腾讯拍拍网(www.paipai.com)是 ...

  9. TFC2017 腾讯Web前端大会参会小结

    简述 上周有幸参加TFC腾讯Web前端大会,见识了各路前端大神的精彩演讲,干货满满的.会议流程分为上午主会场,以及下午的三个分会场.分享的主题涵盖Web新技术.Node.js.框架.工程化. 图形处理 ...

随机推荐

  1. 卸载(uninstalled)Mac os Jenkins pkg 安装包

    有些小伙伴不熟悉Jenkins, 在mac上安装,会选择pkg 安装包, 安装后又想卸载,苦于卸载不干净,今天给到一个命令即可搞定. 对应qq群号:616961231打开终端输入下面命令'/Libra ...

  2. 新的 Centos 服务器初始化配置

    当你初次创建新的 Centos 服务器的时候, Centos 默认的配置安全性和可用性上会存在一点缺陷(运维人员往往会有初始化的脚本).为了增强服务器的安全性和可用性,有些配置你应该尽快地完成. 这篇 ...

  3. svn状态与常见错误

    TortoiseSVN 1.6.16是最后一个目录独立管理自身cache的svn版本(每个目录下都有一个隐藏的.svn文件夹) 之后的版本会则会根目录上统一进行管理(只有根目录下有一个隐藏的.svn文 ...

  4. 商品描述里包含了英文双引号,ERP无法同步菜品信息

    1. 2.因菜品描述里包含英文双引号,破坏了json格式,导致json格式错乱,ERP无法解析,所以无法同步数据.

  5. java----构造回文字符串java(动态规划)【手写演算残图】

    问题描述 草稿解决过程 (字丑别喷) 代码实现 import java.util.Scanner; /** * Created by Admin on 2017/3/26. */ public cla ...

  6. Linux atop 监控系统状态

    atop是一个功能非常强大的linux服务器监控工具,它的数据采集主要包括:CPU.内存.磁盘.网络.进程等,并且内容非常的详细,特别是当那一部分存在压力它会以特殊的颜色进行展示,如果颜色是红色那么说 ...

  7. 你的MySQL服务器开启SSL了吗?SSL在https和MySQL中的原理思考

    最近,准备升级一组MySQL到5.7版本,在安装完MySQL5.7后,在其data目录下发现多了很多.pem类型的文件,然后通过查阅相关资料,才知这些文件是MySQL5.7使用SSL加密连接的.本篇主 ...

  8. 使用Three.js挖空安装门来解决重叠闪烁的问题

    一.挖空原理说明 subtract 用墙面减去与门重叠的部分,产生一个新的对象,导入材质安装门即可 //参与减去几何体 //平行于x轴门 var meshH4Door = new ThreeBSP( ...

  9. VGG网络

    VGG论文给出了一个非常振奋人心的结论:卷积神经网络的深度增加和小卷积核的使用对网络的最终分类识别效果有很大的作用.记得在AlexNet论文中,也做了最后指出了网络深度的对最终的分类结果有很大的作用. ...

  10. css3 object-fit详解

    上传头像的时候遇到了头像变形的问题,最后通过object-fit: cover完美解决了.这个CSS属性可以达到最佳最完美的居中自动剪裁图片的功能. object-fit理解 CSS3 backgro ...