如何使用proxy实现一个简单完整的MVVM库的示例代

MVVM 是当前时代前端日常业务开发中的必备模式(相关框架如react,vue,angular 等), 使用 MVVM 可以将开发者的精力更专注于业务上的逻辑,而不需要关心如何操作 dom。虽然现在都 9012 年了,mvvm 相关原理的介绍已经烂大街了,但出于学习基础知识的目的(使用 proxy 实现的 vue3.0 还在开发中), 在参考了之前 vue.js 的整体思路之后,自己动手实现了一个简易的通过 proxy 实现的 mvvm。

本项目代码已经开源在github,项目正在持续完善中,欢迎交流学习,喜欢请点个 star 吧!

最终效果

<html> <body> <div> <div>{{title}}</div> </div> </body> </html>

import MVVM from '@fe_korey/mvvm'; new MVVM({ view: document.getElementById('app'), model: { title: 'hello mvvm!' }, mounted() { console.log('主程编译完成,欢迎使用MVVM!'); } });

结构概览

Complier 模块实现解析、收集指令,并初始化视图

Observer 模块实现了数据的监听,包括添加订阅者和通知订阅者

Parser 模块实现解析指令,提供该指令的更新视图的更新方法

Watcher 模块实现建立指令与数据的关联

Dep 模块实现一个订阅中心,负责收集,触发数据模型各值的订阅列表

流程为:Complier收集编译好指令后,根据指令不同选择不同的Parser,根据Parser在Watcher中订阅数据的变化并更新初始视图。Observer监听数据变化然后通知给 Watcher ,Watcher 再将变化结果通知给对应Parser里的 update 刷新函数进行视图的刷新。

如何使用proxy实现一个简单完整的MVVM库的示例代

模块详解

Complier

将整个数据模型 data 传入Observer模块进行数据监听

this.$data = new Observer(option.model).getData();

循环遍历整个 dom,对每个 dom 元素的所有指令进行扫描提取

function collectDir(element) { const children = element.childNodes; const childrenLen = children.length; for (let i = 0; i < childrenLen; i++) { const node = children[i]; const nodeType = node.nodeType; if (nodeType !== 1 && nodeType !== 3) { continue; } if (hasDirective(node)) { this.$queue.push(node); } if (node.hasChildNodes() && !hasLateCompileChilds(node)) { collectDir(element); } } }

对每个指令进行编译,选择对应的解析器Parser

const parser = this.selectParsers({ node, dirName, dirValue, cs: this });

将得到的解析器Parser传入Watcher,并初始化该 dom 节点的视图

const watcher = new Watcher(parser); parser.update({ newVal: watcher.value });

所有指令解析完毕后,触发 MVVM 编译完成回调$mounted()

this.$mounted();

使用文档碎片document.createDocumentFragment()来代替真实 dom 节点片段,待所有指令编译完成后,再将文档碎片追加回真实 dom 节点

let child; const fragment = document.createDocumentFragment(); while ((child = this.$element.firstChild)) { fragment.appendChild(child); } //解析完后 this.$element.appendChild(fragment); delete $fragment;

Parser

在Complier模块编译后的指令,选择不同听解析器解析,目前包括ClassParser,DisplayParser,ForParser,IfParser,StyleParser,TextParser,ModelParser,OnParser,OtherParser等解析模块。

switch (name) { case 'text': parser = new TextParser({ node, dirValue, cs }); break; case 'style': parser = new StyleParser({ node, dirValue, cs }); break; case 'class': parser = new ClassParser({ node, dirValue, cs }); break; case 'for': parser = new ForParser({ node, dirValue, cs }); break; case 'on': parser = new OnParser({ node, dirName, dirValue, cs }); break; case 'display': parser = new DisplayParser({ node, dirName, dirValue, cs }); break; case 'if': parser = new IfParser({ node, dirValue, cs }); break; case 'model': parser = new ModelParser({ node, dirValue, cs }); break; default: parser = new OtherParser({ node, dirName, dirValue, cs }); }

不同的解析器提供不同的视图刷新函数update(),通过update更新dom视图

//text.js function update(newVal) { this.el.textContent = _toString(newVal); }

OnParser 解析事件绑定,与数据模型中的 methods字段对应

//详见 https://github.com/zhaoky/mvvm/blob/master/src/core/parser/on.ts el.addEventListener(handlerType, e => { handlerFn(scope, e); });

ForParser 解析数组

详见 https://github.com/zhaoky/mvvm/blob/master/src/core/parser/for.ts

ModelParser 解析双向绑定,目前支持input[text/password] & textarea,input[radio],input[checkbox],select四种情况的双向绑定,双绑原理:

数据变化更新表单:跟其他指令更新视图一样,通过update方法触发更新表单的value

function update({ newVal }) { this.model.el.value = _toString(newVal); }

表单变化更新数据:监听表单变化事件如input,change,在回调里set数据模型

this.model.el.addEventListener('input', e => { model.watcher.set(e.target.value); });

Observer

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/7459e9cd52955c33e5221a8caaed5e5f.html