当前位置: 首页 > Web前端 > HTML

petite-vue源码分析-属性绑定`v-bind`的工作原理

时间:2023-03-28 01:58:39 HTML

关于指令(directive)属性绑定,事件绑定和v-modal底层都是通过指令(directive)实现的,那么什么是指令呢?让我们看一下Directive的定义。//文件./src/directives/index.tsexportinterfaceDirective{(ctx:DirectiveContext):(()=>void)|void}指令(directive)实际上是一个指令,它接受DirectiveContext类型的参数,并返回一个清理函数或者一个什么都不返回的函数。那么DirectiveContext是什么样的呢?//File./src/directives/index.tsexportinterfaceDirectiveContext{el:Tget:(exp?:string)=>any//获取表达式字符串运算结果effect:typeofrawEffect//用添加副作用函数exp:string//expressionstringarg?:string//v-bind:valueorvaluein:value,v-on:clickorclickmodifiersin@click?:Record//preventctx:Contextin@click.prevent}深入v-bind工作原理walk方法在解析模板时会遍历元素的属性集合el.attributes,当属性名匹配到v-bind或:时,调用processDirective(el,'v-bind',value,ctx)对属性名进行处理,转发给对应的指令函数执行。//File./src/walk.ts//为了阅读方便,我删除了所有与v-bind无关的代码constprocessDirective=(el:Element,raw,string,//属性名exp:string,//Attributevalue:expressionstringctx:Context)=>{letdir:指令letarg:string|未定义的let修饰符:Record|undefined//v-bind只有一个修饰符,即camelif(raw[0]==':'){dir=bindarg=raw.slice(1)}else{constargIndex=raw.indexOf(':')//由于指令必须以`v-`开头,因此dirName从第三个字符开始截取。constdirName=argIndex>0?raw.slice(2,argIndex):raw.slice(2)//优先获取内置指令,如果搜索失败,则搜索当前上下文指令dir=builtInDirectives[dirName]||ctx.dirs[dirName]arg=argIndex>0?raw.slice(argIndex):undefined}if(dir){//由于ref没有用来设置元素的属性,所以需要特殊处理if(dir===bind&&arg==='ref')dir=refapplyDirective(el,dir,exp,ctx,arg,modifiers)}}当processDirective匹配到对应的指令并根据属性名提取入参时,就会调用applyDirective通过对应的指令执行操作.//File./src/walk.tsconstapplyDirective=(el:Node,dir:Directive,exp:string,ctx:Context,arg?:stringmodifiers?:Record)=>{constget=(e=exp)=>evaluate(ctx.scope,e,el)//指令执行后可能会返回cleanup函数进行资源释放操作,也可能什么都不返回constcleanup=dir({el,get,effect:ctx.effect,ctx,exp,arg,modifiers})if(cleanup){//给当前上下文添加清理函数,当上下文被销毁时,会执行指令的清理ctx.cleanups.push(cleanup)}}现在终于到了指令绑定执行阶段//file./src/directives/bind.ts//AttributeconstforceAttrRE=/^(spellcheck|draggable|form|list|type)$/exportconstbind:Directive=>({el,get,effect,arg,modifiers})=>{letprevValue:anyif(arg==='class'){el._class=el.className}effect(()=>{letvalue=get()if(arg){//用于处理ev-bind:style="{color:'#fff'}"if(修饰符?.骆驼){arg=camelize(arg)}setProp(el,arg,value,prevValue)}else{//用于处理v-bind="{style:{color:'#fff'},fontSize:'10px'}"for(constkeyinvalue){setProp(el,key,value[key],prevValue&&prevValue[key])}//删除原视图中存在但当前渲染的新视图中不存在的属性for(constkeyinprevValue){if(!value||!(keyinvalue)){setProp(el,key,null)}}}prevValue=value})}constsetProp=(el:Element&{_class?:string},key:string,value:any,prevValue?:any)=>{if(key==='class'){el.setAttribute('class',normalizeClass(el._class?[el._class,value]:value)||'')}elseif(key==='style'){value=normalizeStyle(value)const{style}=elasHTMLElementif(!value){//如果`:style=""`,移除属性styleel.removeAttribute('style')}elseif(isString(value)){if(value!==prevValue)style.cssText=value}else{//value为对象的场景for(constkeyinvalue){setStyle(style,key,value[key])}//删除原视图中存在而在原视图中不存在的样式属性当前呈现的新视图(!(elinstanceofSVGElement)&&keyinel&&!forceAttrRE.test(key)){//设置DOM属性(属性类型可以是对象)el[key]=value//为`v-modal保留`if(key==='value'){el._value=value}}else{//设置DOM属性(属性值只能是字符串类型)/*由于`的属性`value``元素只能存储字符串,*通过`:true-value`和`:false-value`选择和取消选择时设置对应的非字符串值*/if(key==='true-value'){;(elasany)._trueValue=value}elseif(key==='false-value'){;(elasany)._falseValue=value}elseif(value!=null){el.setAttribute(key,value)}else{el.removeAttribute(key)}}}constimportantRE=/\s*!important/constsetStyle=(style:CSSStyleDeclaration,name:string,val:string|string[])=>{if(isArray(val)){val.forEach(v=>setStyle(style,name,v))}else{if(name.startsWith('--')){//自定义属性style.setProperty(name,val)}else{if(importantRE.test(val)){//带有`!important`的属性style.setProperty(hyphenate(name),val.replace(importantRE,''),'important')}else{//公共属性style[nameasany]=val}}}}总结通过这篇文章,我们不仅可以使用v-bind:style绑定单个属性,而且也可以使用通过v-bind一次性绑定多个属性,虽然现在看起来不推荐这样做>_<以后我们会在中了解v-on事件绑定的工作原理深度,敬请期待。《Petite-Vue源码剖析》小册子《Petite-Vue源码剖析》结合实例从在线渲染、响应式系统、沙箱模型逐行解读源码,同时也详细分析了响应式系统中使用JS引擎的SMI优化依赖清理算法.绝对是Vue3源码入门前的绝佳敲门砖。喜欢的话记得转发和欣赏哦!