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的简单实现,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!