如何实现一个MVVM

说一说我对于mvvm模型的理解吧

我第一次接触mvvm也是在学习vue的时候,在我看来vue和react都是数据驱动视图,但是vue属于标准的mvvm模型,react是从组件化演变而来

不多废话,直接粘图

第一次使用mvvm的时候感觉特别的神奇,我只是修改了数据就可以驱动视图的改变
  • 学习mvvm模型的作用
一开始就是在学习vue的使用还有vuex等等很多,也能做一些小的网站,但就是没有办法提升自己的vue到一个更高的境界,后来就不断的往深了学习
听过网上的这么一句话

编程世界和武侠世界比较像,每一个入门的程序员,都幻想自己有朝一日,神功大成,青衣长剑,救民于水火之中,但是其实大部分的人一开始学习方式就错了,导致一直无法进入高手的行列,就是过于看中招式,武器,而忽略了内功的修炼,所以任你慕容复有百家武学,还有被我乔峰一招制敌,所以这就是内功差距

原理就是内功修炼的捷径
进入主题
  • 原理

Object.defineProperty(obj,name,{get:function(),set:function()})

  • 手写

mvvm主要分为两部

  • kvue.js
  1. 获取数据,先获取options
  2. 把options.data的数据通过Object.key()解析
  3. 进入主题 Obejct.defineProprety() 进行双向绑定
  4. 接下来是两个类 Dep 和 Watcher (关系可以看上面的图片)
  • compile.js
  1. 获取dom宿主节点 options.el
  2. 把宿主节点拿出来遍历,高效 createDocumentFragment()
  3. 编译过程 判断是否是文本节点,如果是文本节点就通过正则的分组获取到{{}}插值表达式中间的值
  4. 更新函数 初始化更新函数,调用Watcher
第一次写,怕说不明白,直接粘上代码

先创建目录结构

代码

  • index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <!-- 插值绑定 -->
      <p>{{name}}</p>
      <!-- 指令解析 -->
      <p k-text="name"></p>
      <p>{{age}}</p>
      <p>
        {{doubleAge}}
      </p>
      <!-- 双向绑定 -->
      <input type="text" k-model="name" />
      <!-- 事件处理 -->
      <button @click="changeName">呵呵</button>
      <!-- html内容解析 -->
      <div k-html="html"></div>
    </div>
    <script src="./compile.js"></script>
    <script src="./kvue.js"></script>

    <script>
      const kaikeba = new KVue({
        el: "#app",
        data: {
          name: "I am test.",
          age: 12,
          html: "<button>这是一个按钮</button>"
        },
        created() {
          console.log("开始啦");
          setTimeout(() => {
            this.name = "我是测试";
          }, 1500);
        },
        methods: {
          changeName() {
            this.name = "残梦a博客园";
            this.age = 1;
          }
        }
      });
    </script>
  </body>
</html>
  • kvue.js
// new KVue({data:{...}})

class KVue {
  constructor(options) {
    this.$options = options;

    // 数据响应化
    this.$data = options.data;
    this.observe(this.$data);

    // 模拟一下watcher创建
    // new Watcher();
    // // 通过访问test属性触发get函数,添加依赖
    // this.$data.test;
    // new Watcher();
    // this.$data.foo.bar;

    new Compile(options.el, this);

    // created执行
    if (options.created) {
        options.created.call(this);
    }
  }

  observe(value) {
    if (!value || typeof value !== "object") {
      return;
    }

    // 遍历该对象
    Object.keys(value).forEach(key => {
      this.defineReactive(value, key, value[key]);
    //   代理data中的属性到vue实例上
      this.proxyData(key);
    });
  }

  // 数据响应化
  defineReactive(obj, key, val) {
    this.observe(val); // 递归解决数据嵌套

    const dep = new Dep();

    Object.defineProperty(obj, key, {
      get() {
        Dep.target && dep.addDep(Dep.target);
        return val;
      },
      set(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        // console.log(`${key}属性更新了:${val}`);
        dep.notify();
      }
    });
  }

  proxyData(key) {
      Object.defineProperty(this, key, {
          get(){
            return this.$data[key]
          },
          set(newVal){
            this.$data[key] = newVal;
          }
      })
  }

}

// Dep:用来管理Watcher
class Dep {
  constructor() {
    // 这里存放若干依赖(watcher)
    this.deps = [];
  }

  addDep(dep) {
    this.deps.push(dep);
  }

  notify() {
    this.deps.forEach(dep => dep.update());
  }
}

// Watcher
class Watcher {
  constructor(vm, key, cb) {
      this.vm = vm;
      this.key = key;
      this.cb = cb;

    // 将当前watcher实例指定到Dep静态属性target
    Dep.target = this;
    this.vm[this.key]; // 触发getter,添加依赖
    Dep.target = null;
  }

  update() {
    // console.log("属性更新了");
    this.cb.call(this.vm, this.vm[this.key]);
  }
}

  • complie.js
// 用法 new Compile(el, vm)

class Compile {
  constructor(el, vm) {
    // 要遍历的宿主节点
    this.$el = document.querySelector(el);

    this.$vm = vm;

    // 编译
    if (this.$el) {
      // 转换内部内容为片段Fragment
      this.$fragment = this.node2Fragment(this.$el);
      // 执行编译
      this.compile(this.$fragment);
      // 将编译完的html结果追加至$el
      this.$el.appendChild(this.$fragment);
    }
  }

  // 将宿主元素中代码片段拿出来遍历,这样做比较高效
  node2Fragment(el) {
    const frag = document.createDocumentFragment();
    // 将el中所有子元素搬家至frag中
    let child;
    while ((child = el.firstChild)) {
      frag.appendChild(child);
    }
    return frag;
  }
  // 编译过程
  compile(el) {
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      // 类型判断
      if (this.isElement(node)) {
        // 元素
        // console.log('编译元素'+node.nodeName);
        // 查找k-,@,:
        const nodeAttrs = node.attributes;
        Array.from(nodeAttrs).forEach(attr => {
          const attrName = attr.name; //属性名
          const exp = attr.value; // 属性值
          if (this.isDirective(attrName)) {
            // k-text
            const dir = attrName.substring(2);
            // 执行指令
            this[dir] && this[dir](node, this.$vm, exp);
          }
          if (this.isEvent(attrName)) {
            const dir = attrName.substring(1); // @click
            this.eventHandler(node, this.$vm, exp, dir);
          }
        });
      } else if (this.isInterpolation(node)) {
        // 文本
        // console.log('编译文本'+node.textContent);
        this.compileText(node);
      }

      // 递归子节点
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }

  compileText(node) {
    // console.log(RegExp.$1);
    this.update(node, this.$vm, RegExp.$1, "text");
  }

  // 更新函数
  update(node, vm, exp, dir) {
    const updaterFn = this[dir + "Updater"];
    // 初始化
    updaterFn && updaterFn(node, vm[exp]);
    // 依赖收集
    new Watcher(vm, exp, function(value) {
      updaterFn && updaterFn(node, value);
    });
  }

  text(node, vm, exp) {
    this.update(node, vm, exp, "text");
  }

  //   双绑
  model(node, vm, exp) {
    // 指定input的value属性
    this.update(node, vm, exp, "model");

    // 视图对模型响应
    node.addEventListener("input", e => {
      vm[exp] = e.target.value;
    });
  }

  modelUpdater(node, value) {
    node.value = value;
  }

  html(node, vm, exp) {
    this.update(node, vm, exp, "html");
  }

  htmlUpdater(node, value) {
    node.innerHTML = value;
  }

  textUpdater(node, value) {
    node.textContent = value;
  }

  //   事件处理器
  eventHandler(node, vm, exp, dir) {
    //   @click="onClick"
    let fn = vm.$options.methods && vm.$options.methods[exp];
    if (dir && fn) {
      node.addEventListener(dir, fn.bind(vm));
    }
  }

  isDirective(attr) {
    return attr.indexOf("k-") == 0;
  }
  isEvent(attr) {
    return attr.indexOf("@") == 0;
  }
  isElement(node) {
    return node.nodeType === 1;
  }
  // 插值文本
  isInterpolation(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的MVVM Demo示例,主要实现了数据绑定和视图模型。 首先,我们需要创建一个模型类和视图模型类。 模型类: ``` class User { String name; int age; User(this.name, this.age); } ``` 视图模型类: ``` import 'package:flutter/material.dart'; import 'user.dart'; class UserViewModel extends ChangeNotifier { User _user; UserViewModel(User user) { _user = user; } String get name => _user.name; set name(String value) { _user.name = value; notifyListeners(); } int get age => _user.age; set age(int value) { _user.age = value; notifyListeners(); } } ``` 在视图上,我们可以将数据绑定到`UserViewModel`, ``` import 'package:flutter/material.dart'; import 'package:mvvm_demo/user.dart'; import 'package:mvvm_demo/user_view_model.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'MVVM Demo', home: ChangeNotifierProvider<UserViewModel>( create: (context) => UserViewModel(User('User Name', 20)), child: HomePage(), ), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { final userViewModel = Provider.of<UserViewModel>(context); return Scaffold( appBar: AppBar( title: Text('MVVM Demo Home'), ), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: EdgeInsets.all(16.0), child: TextField( decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Name', ), onChanged: (value) { userViewModel.name = value; }, ), ), Container( margin: EdgeInsets.all(16.0), child: TextField( decoration: InputDecoration( border: OutlineInputBorder(), labelText: 'Age', ), onChanged: (value) { userViewModel.age = int.parse(value); }, ), ), Container( margin: EdgeInsets.all(16.0), child: Text( 'User Name: ${userViewModel.name} \nUser Age: ${userViewModel.age}'), ), ], ), ); } } ``` 在这个示例中,我们使用`provider`库来管理视图模型的状态,当视图模型中的数据发生变化时,我们可以通过`notifyListeners()`方法通知视图重新渲染。 希望这个简单的MVVM Demo示例对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值