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

Vue.js3.x双向绑定原理

时间:2023-03-27 17:09:58 JavaScript

什么是双向绑定?话不多说,先来看一个v-model的基本例子:所以,它和我们一般的自定义指令一样,需要实现Vue.js生命周期的钩子函数。其次,v-model实现了双向绑定,即:数据单向流向DOM,单向DOM流向数据。理解了以上两点,再看代码就会清楚很多。//packages/runtime-dom/src/directives/vModel.tsexportconstvModelText:ModelDirective={created(){},mounted(){},beforeUpdate(){}}打开v-model的源码我们可以看到它实现了对应的Vue.js生命周期钩子函数,其实就是一个内置的-在自定义指令中。那么,v-model是如何实现双向绑定的呢?具体是如何实现数据到DOM的单向流动和DOM到数据的单向流动。数据到DOM的单向流//packages/runtime-dom/src/directives/vModel.tsexportconstvModelText:ModelDirective={//在mounted上设置值,使其在type="range"mounted(el,{value})的最小值/最大值之后{el.value=value==null?'':value}}数据单向流向DOM的实现很简单,一行代码就搞定了,就是绑定v-model的值赋值给el.value。从DOM到数据的单向流//packages/runtime-dom/src/directives/vModel.tsexportconstvModelText:ModelDirective={created(el,{modifiers:{lazy,trim,number}},vnode){el._assign=getModelAssigner(vnode)//参见:https://github.com/vuejs/core/issues/3813constcastToNumber=数字||(vnode.props&&vnode.props.type==='number')//实现惰性函数addEventListener(el,lazy?'change':'input',e=>{//不为数据分配DOM值时`composing=true`if((e.targetasany).composing)returnletdomValue:string|number=el.valueif(trim){domValue=domValue.trim()}elseif(castToNumber){domValue=toNumber(domValue)}//当DOM的值发生变化时,同时改变对应的数据(即改变v-model上绑定的变量的值)el._assign(domValue)})//实现trimfunctionif(trim){addEventListener(el,'change',()=>{el.value=el.value.trim()})}//不配置lazy时,监听input的输入事件,当用户实时输入时会触发//另外,会多监听compositionstart和compositionend事件。if(!lazy){//这是因为当用户开始使用拼音输入法输入汉字时,会触发该事件。//此时设置`composing=true`,可以在输入事件回调中进行判断,避免将DOM值赋值给数据,//因为此时输入不完整。addEventListener(el,'compositionstart',onCompositionStart)//当用户从输入法中选择了一些数据并完成输入(比如普通的中文输入法,按空格确认输入文本),//设置`composing=false`,手动触发onCompositionEnd中的input事件完成数据赋值。addEventListener(el,'compositionend',onCompositionEnd)//Safari<10.2&UIWebView不会触发compositionendwhen//在确认组合选择之前切换焦点//这也解决了某些浏览器的问题,例如iOSChrome//在自动完成时触发“更改”而不是“输入”。addEventListener(el,'change',onCompositionEnd)}}}functiononCompositionStart(e:Event){(e.targetasany).composing=true}functiononCompositionEnd(e:Event){consttarget=e.targetasanyif(target.composing){target.composing=falsetarget.dispatchEvent(newEvent('input'))}}constgetModelAssigner=(vnode:VNode):AssignerFn=>{constfn=vnode.props!['onUpdate:modelValue']返回isArray(fn)?value=>invokeArrayFns(fn,value):fn}代码有点多,但是原理很简单:通过自定义监听事件addEventListener监听input元素的输入或者输入,change事件执行相应的函数时用户手动输入数据,通过el.value调用el._assig获取输入的新值n(onUpdate:modelValue属性对应的函数)方法v-model绑定value,实现DOM到data的单向流。关键在于onUpdate:modelValue借助Vue3TemplateExplorer,我们可以查看编译后生成的render函数,可以发现它的作用并没有什么神奇之处,就是帮我们自动更新的值v模型上的变量绑定。import{vModelTextas_vModelText,withDirectivesas_withDirectives,openBlockas_openBlock,createElementBlockas_createElementBlock}from"vue"导出函数render(_ctx,_cache,$props,$setup,$data,$options){return_withDirectives((_openBlock(),_createElementBlock("input",{type:"text",//`onUpdate:modelValue`的作用,//是为我们自动更新`v-绑定在model上的变量值。"onUpdate:modelValue":$event=>((_ctx.search)=$event)},null,8/*PROPS*/,["onUpdate:modelValue"])),[[_vModelText,_ctx.search]])}另外还有lazy处理,trim处理,数字处理,解决了输入时文本被清空的问题。onCompositionStart和onCompositionEnd的功能详见textaddedwithIMEtoinputthathasv-modelisgonewhentheviewisupdated#2302。一句话总结:使用addEventListener实现DOM到数据的单向流。最后执行beforeUpdate,如果数据的值与DOM的值不一致,则将数据更新到DOM中://packages/runtime-dom/src/directives/vModel.tsbeforeUpdate(el,{value,modifiers:{lazy,trim,number}},vnode){el._assign=getModelAssigner(vnode)//避免清除未解析的文本。#2302//当输入中文等某些语言时,当输入未完成时,更新Clear时会自动清除已有文本,详见issue#2302if((elasany).composing)returnif(document.activeElement===el){if(lazy){return}if(trim&&el.value.trim()===value){return}if((number||el.type==='number')&&toNumber(el.value)===value){return}}constnewValue=value==null?'':valueif(el.value!==newValue){el.value=newValue}}以上就是text类型的input元素的双向绑定原理。当然input元素类型不止于此,还有radio、checkbox等其他类型。有兴趣的可以自己看下,不过原理是一样的,就是实现两个功能:从数据到DOM的单向流,从DOM到数据的单向流。