vue 中directive功能的简单实现
2018年首个计划是学习vue源码,查阅了一番资料之后,决定从第一个commit开始看起,这将是一场持久战!本篇介绍directive的简单实现,主要学习其实现的思路及代码的设计(directive和filter扩展起来非常方便,符合设计模式中的开闭原则)。
构思API
Toggleclass
实现功能够简单吧--将scope中的数据绑定到app中。
核心逻辑设计
指令格式
以sd-text="msg|capitalize"为例说明:
- sd为统一的前缀标识
- text为指令名称
- capitalize为过滤器名称
其中|后面为过滤器,可以添加多个。sd-class-red中的red为参数。
代码结构介绍
main.js入口文件
//Seed构造函数 constSeed=function(opts){ }; //对外暴露的API module.exports={ create:function(opts){ returnnewSeed(opts); } }; directives.js module.exports={ text:function(el,value){ el.textContent=value||''; } }; filters.js module.exports={ capitalize:function(value){ value=value.toString(); returnvalue.charAt(0).toUpperCase()+value.slice(1); } };
就这三个文件,其中directives和filters都是配置文件,很易于扩展。
实现的大致思路如下:
1.在Seed实例创建的时候会依次解析el容器中node节点的指令
2.将指令解析结果封装为指令对象,结构为:
3.想办法执行指令的update方法即可,该插件使用了Object.defineProperty来定义scope中的每个属性,在其setter中触发指令的update方法。
核心代码
constprefix='sd'; constDirectives=require('./directives'); constFilters=require('./filters'); //结果为[sd-text],[sd-class],[sd-on]的字符串 constselector=Object.keys(Directives).map((name)=>`[${prefix}-${name}]`).join(','); constSeed=function(opts){ constself=this, root=this.el=document.getElementById(opts.id), //筛选出el下所能支持的directive的nodes列表 els=this.el.querySelectorAll(selector), bindings={}; this.scope={}; //解析节点 [].forEach.call(els,processNode); //解析根节点 processNode(root); //给scope赋值,触发setter方法,此时会调用与其相对应的directive的update方法 Object.keys(bindings).forEach((key)=>{ this.scope[key]=opts.scope[key]; }); functionprocessNode(el){ cloneAttributes(el.attributes).forEach((attr)=>{ constdirective=parseDirective(attr); if(directive){ bindDirective(self,el,bindings,directive); } }); } };
可以看到核心方法processNode主要做了两件事一个是parseDirective,另一个是bindDirective。
先来看看parseDirective方法:
functionparseDirective(attr){ if(attr.name.indexOf(prefix)==-1)return; //解析属性名称获取directive constnoprefix=attr.name.slice(prefix.length+1), argIndex=noprefix.indexOf('-'), dirname=argIndex===-1?noprefix:noprefix.slice(0,argIndex), arg=argIndex===-1?null:noprefix.slice(argIndex+1), def=Directives[dirname] //解析属性值获取filters constexp=attr.value, pipeIndex=exp.indexOf('|'), key=(pipeIndex===-1?exp:exp.slice(0,pipeIndex)).trim(), filters=pipeIndex===-1?null:exp.slice(pipeIndex+1).split('|').map((filterName)=>filterName.trim()); returndef?{ attr:attr, key:key, filters:filters, argument:arg, definition:Directives[dirname], update:typeofdef==='function'?def:def.update }:null; }
以sd-on-click="toggle|.button"为例来说明,其中attr对象的name为sd-on-click,value为toggle|.button,最终解析结果为:
{ "attr":attr, "key":"toggle", "filters":[".button"], "argument":"click", "definition":{"on":{}}, "update":function(){} }
紧接着调用bindDirective方法
/** *数据绑定 *@param{Seed}seedSeed实例对象 *@param{Element}el当前node节点 *@param{Object}bindings数据绑定存储对象 *@param{Object}directive指令解析结果 */ functionbindDirective(seed,el,bindings,directive){ //移除指令属性 el.removeAttribute(directive.attr.name); //数据属性 constkey=directive.key; letbinding=bindings[key]; if(!binding){ bindings[key]=binding={ value:undefined, directives:[]//与该数据相关的指令 }; } directive.el=el; binding.directives.push(directive); if(!seed.scope.hasOwnProperty(key)){ bindAccessors(seed,key,binding); } } /** *重写scope西乡属性的getter和setter *@param{Seed}seedSeed实例 *@param{String}key对象属性即opts.scope中的属性 *@param{Object}binding数据绑定关系对象 */ functionbindAccessors(seed,key,binding){ Object.defineProperty(seed.scope,key,{ get:function(){ returnbinding.value; }, set:function(value){ binding.value=value; //触发directive binding.directives.forEach((directive)=>{ //如果有过滤器则先执行过滤器 if(typeofvalue!=='undefined'&&directive.filters){ value=applyFilters(value,directive); } //调用update方法 directive.update(directive.el,value,directive.argument,directive); }); } }); } /** *调用filters依次处理value *@param{任意类型}value数据值 *@param{Object}directive解析出来的指令对象 */ functionapplyFilters(value,directive){ if(directive.definition.customFilter){ returndirective.definition.customFilter(value,directive.filters); }else{ directive.filters.forEach((name)=>{ if(Filters[name]){ value=Filters[name](value); } }); returnvalue; } }
其中的bindings存放了数据和指令的关系,该对象中的key为opts.scope中的属性,value为Object,如下:
{ "msg":{ "value":undefined, "directives":[]//上面介绍的directive对象 } }
数据与directive建立好关系之后,bindAccessors中为seed的scope对象的属性重新定义了getter和setter,其中setter会调用指令update方法,到此就已经完事具备了。
Seed构造函数在实例化的最后会迭代bindings中的key,然后从opts.scope找到对应的value,赋值给了scope对象,此时setter中的update就触发执行了。
下面再看一下sd-on指令的定义:
{ on:{ update:function(el,handler,event,directive){ if(!directive.handlers){ directive.handlers={}; } consthandlers=directive.handlers; if(handlers[event]){ el.removeEventListener(event,handlers[event]); } if(handler){ handler=handler.bind(el); el.addEventListener(event,handler); handlers[event]=handler; } }, customFilter:function(handler,selectors){ returnfunction(e){ constmatch=selectors.every((selector)=>e.target.matches(selector)); if(match){ handler.apply(this,arguments); } } } } }
发现它有customFilter,其实在applyFilters中就是针对该指令做的一个单独的判断,其中的selectors就是[".button"],最终返回一个匿名函数(事件监听函数),该匿名函数当做value传递给update方法,被其handler接收,update方法处理的是事件的绑定。这里其实实现的是事件的代理功能,customFilter中将handler包装一层作为事件的监听函数,同时还实现事件代理功能,设计的比较巧妙!
总结
以上所述是小编给大家介绍的vue中directive的简单实现,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!