前言
還是上一篇面試官:來說說vue3是怎么處理內(nèi)置的v-for、v-model等指令? 文章的那個(gè)粉絲,面試官接著問了他另外一個(gè)v-model的問題。
-
面試官:vue3的v-model都用過吧,來講講。
-
粉絲:v-model其實(shí)就是一個(gè)語法糖,在編譯時(shí)v-model會(huì)被編譯成
:modelValue
屬性和@update:modelValue
事件。一般在子組件中定義一個(gè)名為modelValue
的props來接收父組件v-model傳遞的值,然后當(dāng)子組件表單的值變化時(shí)再使用@update:modelValue
拋出事件給父組件,由父組件來更新v-model綁定的變量。 -
面試官:你說的這個(gè)是在組件上面使用v-model,原生input上面也支持v-model,你來說說原生input上面使用v-model以及和組件上面使用v-model有什么區(qū)別?
-
粉絲:啊,兩個(gè)不是一樣的嗎?都是
:modelValue
屬性和@update:modelValue
事件的語法糖吖。 -
面試官:原生input標(biāo)簽接收的是value屬性,監(jiān)聽的是input或者change事件。你說v-model會(huì)編譯成
:modelValue
屬性,但是input標(biāo)簽只接收value屬性,那你傳的modelValue
屬性input標(biāo)簽怎么接收的?同理你說v-model會(huì)編譯成監(jiān)聽@update:modelValue
事件,但是input標(biāo)簽只監(jiān)聽input或者change事件,那你傳監(jiān)聽的@update:modelValue
事件又是怎么觸發(fā)的呢?
在之前的 面試官:只知道v-model是modelValue語法糖,那你可以走了 文章中我已經(jīng)講過了在組件中怎么將v-model編譯成:modelValue
屬性和@update:modelValue
事件,今天我們就來講講在原生input上面使用v-model和在組件上面使用有什么區(qū)別?
先說答案
來看看我畫個(gè)這個(gè)流程圖,如下:
根據(jù)上面的流程圖,我們知道了在組件上面使用v-model和原生input上面使用v-model區(qū)別主要有三點(diǎn):
-
組件上面的v-model編譯后會(huì)生成
modelValue
屬性和@update:modelValue
事件。而在原生input上面使用v-model編譯后不會(huì)生成
modelValue
屬性,只會(huì)生成onUpdate:modelValue
回調(diào)函數(shù)和vModelText
自定義指令。(在 面試官:只知道v-model是modelValue語法糖,那你可以走了 文章中我們已經(jīng)講過了@update:modelValue
事件其實(shí)等價(jià)于onUpdate:modelValue
回調(diào)函數(shù)) -
在組件上面使用v-model,是由子組件中定義一個(gè)名為
modelValue
的props來接收父組件使用v-model綁定的變量,然后使用這個(gè)modelValue
綁定到子組件的表單中。在原生input上面使用v-model,是由編譯后生成的
vModelText
自定義指令在mounted
和beforeUpdate
鉤子函數(shù)中去將v-model綁定的變量值更新到原生input輸入框的value屬性,以保證v-model綁定的變量值和input輸入框中的值始終一致。 -
在組件上面使用v-model,是由子組件使用emit拋出
@update:modelValue
事件,在@update:modelValue
的事件處理函數(shù)中去更新v-model綁定的變量。而在原生input上面使用v-model,是由編譯后生成的
vModelText
自定義指令在created
鉤子函數(shù)中去監(jiān)聽原生input標(biāo)簽的input或者change事件。在事件回調(diào)函數(shù)中去手動(dòng)調(diào)用onUpdate:modelValue
回調(diào)函數(shù),然后在回調(diào)函數(shù)中去更新v-model綁定的變量。文章來源:http://www.zghlxwxcb.cn/news/detail-856576.html
看個(gè)例子
下面這個(gè)是我寫的一個(gè)demo,代碼如下:
<template>
<input v-model="msg" />
<p>input value is: {{ msg }}</p>
</template>
<script setup lang="ts">
import { ref } from "vue";
const msg = ref();
</script>
上面的例子很簡單,在原生input標(biāo)簽上面使用v-model綁定了msg
變量。我們接下來看看編譯后的js代碼是什么樣的,那么問題來了怎么找到編譯后的js代碼呢?
其實(shí)很簡單直接在network上面找到你的那個(gè)vue文件就行了,比如我這里的文件是index.vue
,那我只需要在network上面找叫index.vue
的文件就行了。但是需要注意一下network上面有兩個(gè)index.vue
的js請(qǐng)求,分別是template模塊+script模塊編譯后的js文件,和style模塊編譯后的js文件。
那怎么區(qū)分這兩個(gè)index.vue
文件呢?很簡單,通過query就可以區(qū)分。由style模塊編譯后的js文件的URL中有type=style的query,如下圖所示:
接下來我們來看看編譯后的index.vue
,簡化的代碼如下:
import {
Fragment as _Fragment,
createElementBlock as _createElementBlock,
createElementVNode as _createElementVNode,
defineComponent as _defineComponent,
openBlock as _openBlock,
toDisplayString as _toDisplayString,
vModelText as _vModelText,
withDirectives as _withDirectives,
ref,
} from "/node_modules/.vite/deps/vue.js?v=23bfe016";
const _sfc_main = _defineComponent({
__name: "index",
setup(__props, { expose: __expose }) {
__expose();
const msg = ref();
const __returned__ = { msg };
return __returned__;
},
});
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
_openBlock(),
_createElementBlock(
_Fragment,
null,
[
_withDirectives(
_createElementVNode(
"input",
{
"onUpdate:modelValue":
_cache[0] || (_cache[0] = ($event) => ($setup.msg = $event)),
},
null,
512
),
[[_vModelText, $setup.msg]]
),
_createElementVNode(
"p",
null,
"input value is: " + _toDisplayString($setup.msg),
1
),
],
64
)
);
}
_sfc_main.render = _sfc_render;
export default _sfc_main;
從上面的代碼中我們可以看到編譯后的js代碼主要分為兩塊。
第一塊是_sfc_main
組件對(duì)象,里面有name屬性和setup方法。一個(gè)vue組件其實(shí)就是一個(gè)對(duì)象,這里的_sfc_main
對(duì)象就是一個(gè)vue組件對(duì)象。
我們接著來看第二塊_sfc_render
,從名字我想你應(yīng)該已經(jīng)猜到了他是一個(gè)render函數(shù)。執(zhí)行這個(gè)_sfc_render
函數(shù)就會(huì)生成虛擬DOM,然后再由虛擬DOM生成瀏覽器上面的真實(shí)DOM。我們接下來主要看看這個(gè)render函數(shù)。
render函數(shù)
這個(gè)render
函數(shù)前面會(huì)調(diào)用openBlock
函數(shù)和createElementBlock
函數(shù)。他的作用是在編譯時(shí)盡可能的提取多的關(guān)鍵信息,可以減少運(yùn)行時(shí)比較新舊虛擬DOM帶來的性能開銷。我們這篇文章不關(guān)注這點(diǎn),所以就不細(xì)講了。
來看看里層的數(shù)組,數(shù)組中有兩項(xiàng)。分別是withDirectives
函數(shù)和createElementVNode
函數(shù),數(shù)組中的這兩個(gè)函數(shù)分別對(duì)應(yīng)的就是template中的input標(biāo)簽和p標(biāo)簽。我們主要來關(guān)注input標(biāo)簽,也就是withDirectives
函數(shù)。
withDirectives函數(shù)
這個(gè)withDirectives
是否覺得有點(diǎn)眼熟?他是vue提供的一個(gè)進(jìn)階API,我們平時(shí)寫業(yè)務(wù)基本不會(huì)用到他。作用是給vnode(虛擬DOM)增加自定義指令。
接收兩個(gè)參數(shù),第一個(gè)參數(shù)為需要添加指令的vnode,第二個(gè)參數(shù)是由自定義指令組成的二維數(shù)組。二維數(shù)組的第一層是表示有哪些自定義指令,第二層表示的是指令名稱、綁定值、參數(shù)、修飾符。第二層的結(jié)構(gòu)為:?[Directive, value, argument, modifiers]
?。如果不需要,可以省略數(shù)組的尾元素。
舉個(gè)例子:
import { h, withDirectives } from 'vue'
// 一個(gè)自定義指令
const pin = {
mounted() {
/* ... */
},
updated() {
/* ... */
}
}
// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [
[pin, 200, 'top', { animate: true }]
])
上面這個(gè)例子定義了一個(gè)pin
的自定義指令,調(diào)用h
函數(shù)生成vnode傳給withDirectives
函數(shù)的第一個(gè)參數(shù)。第二個(gè)參數(shù)自定義指令數(shù)組,我們這里只傳了一個(gè)pin
自定義指令。來看看[Directive, value, argument, modifiers]
。
-
第一個(gè)
Directive
字段:“指令名稱”對(duì)應(yīng)的就是pin
自定義指令。 -
第二個(gè)
value
字段:“指令值”對(duì)應(yīng)的就是200。 -
第三個(gè)字段
argument
字段:“參數(shù)”對(duì)應(yīng)的就是top
參數(shù)。 -
第四個(gè)字段
modifiers
字段:“修飾符”對(duì)應(yīng)的就是animate
修飾符。
所以上面的withDirectives
函數(shù)實(shí)際就是對(duì)應(yīng)的<div v-pin:top.animate="200"></div>
createElementVNode函數(shù)
看見這個(gè)函數(shù)名字我想你應(yīng)該也猜到了,作用是創(chuàng)建vnode(虛擬dom)。這個(gè)函數(shù)和vue提供的 h函數(shù)差不多,底層調(diào)用的都是一個(gè)名為createBaseVNode
的函數(shù)。接收的第一個(gè)參數(shù)既可以是一個(gè)字符串 (用于原生元素) 也可以是一個(gè) Vue 組件定義。接收的第二個(gè)參數(shù)是要傳遞的 prop,第三個(gè)參數(shù)是子節(jié)點(diǎn)。
舉個(gè)例子:
createElementVNode("input", {
value: 12,
})
上面這個(gè)例子創(chuàng)建了一個(gè)input的vnode,輸入框中的值為12
搞清楚了withDirectives
函數(shù)和createElementVNode
函數(shù)的作用,我們回過頭來看之前對(duì)應(yīng)input標(biāo)簽的代碼你應(yīng)該就很容易理解了。代碼如下:
_withDirectives(
_createElementVNode(
"input",
{
"onUpdate:modelValue":
_cache[0] || (_cache[0] = ($event) => ($setup.msg = $event)),
},
null,
512
),
[[_vModelText, $setup.msg]]
)
調(diào)用withDirectives
函數(shù),傳入兩個(gè)參數(shù)。第一個(gè)參數(shù)為調(diào)用createElementVNode
函數(shù)生成input的vnode。第二個(gè)參數(shù)為傳入的自定義指令組成的數(shù)組,很明顯這里的二維數(shù)組的第一層只有一項(xiàng),說明只傳入了一個(gè)自定義指令。
回憶一下前面說的二維數(shù)組中的第二層的結(jié)構(gòu):?[Directive, value, argument, modifiers]
,第一個(gè)字段Directive
表示這里傳入了一個(gè)名為vModelText
的自定義指令,第二個(gè)字段value
表示給vModelText
指令綁定的值為$setup.msg
。我們?cè)?Vue 3 的 setup語法糖到底是什么東西?文章中已經(jīng)講過了,這里的$setup.msg
實(shí)際就是指向的是setup中定義的名為msg
的ref變量。
我們?cè)賮砜蠢锩娴?code>createElementVNode函數(shù),創(chuàng)建一個(gè)input的vnode。傳入了一個(gè)名為onUpdate:modelValue
的props屬性,屬性值是一個(gè)經(jīng)過緩存的回調(diào)函數(shù)。
為什么需要緩存呢?因?yàn)槊看胃马撁娑紩?huì)執(zhí)行一次render函數(shù),每次執(zhí)行render函數(shù)都會(huì)調(diào)用一次createElementVNode
函數(shù)。如果不緩存那不就變成了每次更新頁面都會(huì)生成一個(gè)onUpdate:modelValue
的回調(diào)函數(shù)。這里的回調(diào)函數(shù)也很簡單,接收一個(gè)$event
變量。這個(gè)$event
變量就是輸入框中輸入的值,然后最新的輸入框中的值同步到setup
中的msg
變量。
總結(jié)一下就是給input標(biāo)簽的vnode添加了一個(gè)vModelText
的自定義指令,并且給指令綁定的值為msg
變量。還有就是在input標(biāo)簽的vnode中添加了一個(gè)onUpdate:modelValue
的屬性,屬性值是一個(gè)回調(diào)函數(shù),觸發(fā)這個(gè)回調(diào)函數(shù)就會(huì)將msg
變量的值更新為輸入框中的最新值。我們知道input輸入框中的值對(duì)應(yīng)的是value屬性,監(jiān)聽的是input和change事件。那么這里有兩個(gè)問題:
-
如何將
vModelText
自定義指令綁定的msg
變量的值傳遞給input輸入框中的value屬性的呢? -
input標(biāo)簽監(jiān)聽input和change事件,編譯后input上面卻是一個(gè)名為
onUpdate:modelValue
的props回調(diào)函數(shù)?
要回答上面的兩個(gè)問題我們需要看vModelText
自定義指令是什么樣的。
vModelText自定義指令
vModelText
是一個(gè)運(yùn)行時(shí)的v-model指令,為什么說是運(yùn)行時(shí)呢? 面試官:只知道v-model是modelValue語法糖,那你可以走了 文章中我們已經(jīng)講過了,在編譯時(shí)就會(huì)將組件上面的v-model指令編譯成modelValue
屬性和@update:modelValue
事件。所以當(dāng)運(yùn)行時(shí)在組件上已經(jīng)沒有了v-model指令了,只有原生input在運(yùn)行時(shí)依然還有v-model指令,也就是vModelText
自定義指令。
我們來看看vModelText
自定義指令的代碼:
const vModelText = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
// ...
},
mounted(el, { value }) {
// ...
},
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
// ...
},
}
從上面可以看到vModelText
自定義指令中使用了三個(gè)鉤子函數(shù):created
、mounted
、beforeUpdate
,我們來看看上面三個(gè)鉤子函數(shù)中使用到的參數(shù):
-
el
:指令綁定到的元素。這可以用于直接操作 DOM。 -
binding
:一個(gè)對(duì)象,包含以下屬性。上面的例子中是直接解構(gòu)了binding
對(duì)象。-
value
:傳遞給指令的值。例如在?v-model="msg"
?中,其中msg
變量的值為“hello word”,value
的值就是“hello word”。 -
modifiers
:一個(gè)包含修飾符的對(duì)象,v-model支持lazy
,trim
,number
這三個(gè)修飾符。-
lazy
:默認(rèn)情況下,v-model
?會(huì)在每次?input
?事件后更新數(shù)據(jù)。你可以添加?lazy
?修飾符來改為在每次?change
?事件后更新數(shù)據(jù),在input輸入框中就是失去焦點(diǎn)時(shí)再更新數(shù)據(jù)。 -
trim
:去除用戶輸入內(nèi)容中兩端的空格。 -
number
:讓用戶輸入自動(dòng)轉(zhuǎn)換為數(shù)字。
-
-
-
vnode
:綁定元素的 VNode(虛擬DOM)。
mounted鉤子函數(shù)
我們先來看mounted
鉤子函數(shù),代碼如下:
const vModelText = {
mounted(el, { value }) {
el.value = value == null ? "" : value;
},
}
mounted
中的代碼很簡單,在mounted
時(shí)如果v-model綁定的msg
變量的值不為空,那么就將msg
變量的值同步到input輸入框中。
created鉤子函數(shù)
我們接著來看created
鉤子函數(shù)中的代碼,如下:
const assignKey = Symbol("_assign");
const vModelText = {
created(el, { modifiers: { lazy, trim, number } }, vnode) {
el[assignKey] = getModelAssigner(vnode);
const castToNumber =
number || (vnode.props && vnode.props.type === "number");
addEventListener(el, lazy ? "change" : "input", (e) => {
if (e.target.composing) return;
let domValue = el.value;
if (trim) {
domValue = domValue.trim();
}
if (castToNumber) {
domValue = looseToNumber(domValue);
}
el[assignKey](domValue);
});
if (trim) {
addEventListener(el, "change", () => {
el.value = el.value.trim();
});
}
if (!lazy) {
addEventListener(el, "compositionstart", onCompositionStart);
addEventListener(el, "compositionend", onCompositionEnd);
}
},
}
created
鉤子函數(shù)中的代碼主要分為五部分。
第一部分
首先我們來看第一部分代碼:
el[assignKey] = getModelAssigner(vnode);
我們先來看這個(gè)getModelAssigner
函數(shù)。代碼如下:
const getModelAssigner = (vnode) => {
const fn = vnode.props["onUpdate:modelValue"];
return isArray(fn) ? (value) => invokeArrayFns(fn, value) : fn;
};
getModelAssigner
函數(shù)的代碼很簡單,就是返回vnode
上面名為onUpdate:modelValue
的props回調(diào)函數(shù)。前面我們已經(jīng)講過了執(zhí)行這個(gè)回調(diào)函數(shù)會(huì)同步更新v-model綁定的msg
變量。
所以第一部分代碼的作用就是取出input標(biāo)簽上面名為onUpdate:modelValue
的props回調(diào)函數(shù),然后賦值給input標(biāo)簽對(duì)象的assignKey
方法上面,后面再輸入框中的input或者chang事件觸發(fā)時(shí)會(huì)手動(dòng)調(diào)用。這個(gè)assignKey
是一個(gè)Symbol,唯一的標(biāo)識(shí)符。
第二部分
再來看第二部分代碼:
const castToNumber =
number || (vnode.props && vnode.props.type === "number");
castToNumber
表示是否使用了.number
修飾符,或者input輸入框上面是否有type=number
的屬性。如果castToNumber
的值為true,后續(xù)處理輸入框的值時(shí)會(huì)將其轉(zhuǎn)換成數(shù)字。
第三部分
我們接著來看第三部分的代碼:
addEventListener(el, lazy ? "change" : "input", (e) => {
if (e.target.composing) return;
let domValue = el.value;
if (trim) {
domValue = domValue.trim();
}
if (castToNumber) {
domValue = looseToNumber(domValue);
}
el[assignKey](domValue);
});
對(duì)input輸入框進(jìn)行事件監(jiān)聽,如果有.lazy
修飾符就監(jiān)聽change事件,否則監(jiān)聽input事件??纯矗@不就和.lazy
修飾符的作用對(duì)上了嘛。.lazy
修飾符的作用是在每次change事件觸發(fā)時(shí)再去更新數(shù)據(jù)。
我們接著看里面的事件處理函數(shù),來看看第一行代碼:
if (e.target.composing) return;
當(dāng)用戶使用拼音輸入法輸入漢字時(shí),正在輸入拼音階段也會(huì)觸發(fā)input事件的。但是一般情況下我們只希望真正合成漢字時(shí)才觸發(fā)input去更新數(shù)據(jù),所以在輸入拼音階段觸發(fā)的input事件需要被return。至于e.target.composing
什么時(shí)候被設(shè)置為true
,什么時(shí)候又是false
,我們接著會(huì)講。
后面的代碼就很簡單了,將輸入框中的值也就是el.value
賦值給domValue
變量。如果使用了.trim
修飾符,就執(zhí)行trim
方法,去除掉domValue
變量中兩端的空格。
如果castToNumber
的值為true,表示使用了.number
修飾符或者在input上面使用了type=number
。調(diào)用looseToNumber
方法將domValue
字符串轉(zhuǎn)換為數(shù)字。
最后將處理后的domValue
,也就是處理后的輸入框中的輸入值,作為參數(shù)調(diào)用el[assignKey]
方法。我們前面講過了el[assignKey]
中存的就是input標(biāo)簽上面名為onUpdate:modelValue
的props回調(diào)函數(shù),執(zhí)行el[assignKey]
方法就是執(zhí)行回調(diào)函數(shù),在回調(diào)函數(shù)中會(huì)將v-model綁定的msg
變量的值更新為處理后的輸入框中的輸入值。
現(xiàn)在你知道了為什么input標(biāo)簽監(jiān)聽input和change事件,編譯后input上面卻是一個(gè)名為onUpdate:modelValue
的props回調(diào)函數(shù)了?
因?yàn)樵趇nput或者change事件的回調(diào)中會(huì)將輸入框的值根據(jù)傳入的修飾符進(jìn)行處理,然后將處理后的輸入框的值作為參數(shù)手動(dòng)調(diào)用onUpdate:modelValue
回調(diào)函數(shù),在回調(diào)函數(shù)中更新綁定的msg變量。
第四部分
我們接著來看第四部分的代碼,如下:
if (trim) {
addEventListener(el, "change", () => {
el.value = el.value.trim();
});
}
這一塊代碼很簡單,如果使用了.trim
修飾符,觸發(fā)change事件,在input輸入框中就是失去焦點(diǎn)時(shí)。就會(huì)將輸入框中的值也trim一下,去掉前后的空格。
為什么需要有這塊代碼,前面在input或者change事件中不是已經(jīng)對(duì)輸入框中的值進(jìn)行trim處理了嗎?而且后面的beforeUpdate
鉤子函數(shù)中也執(zhí)行了el.value = newValue
將輸入框中的值更新為v-model綁定的msg
變量的值。
答案是:前面確實(shí)對(duì)輸入框中拿到的值進(jìn)行trim處理,然后將trim處理后的值更新為v-model綁定的msg變量。但是我們并沒有將輸入框中的值更新為trim處理后的,雖然在beforeUpdate
鉤子函數(shù)中會(huì)將輸入框中的值更新為v-model綁定的msg變量。但是如果只是在輸入框的前后輸入空格,那么經(jīng)過trim處理后在beforeUpdate
鉤子函數(shù)中就會(huì)認(rèn)為輸入框中的值和msg
變量的值相等。就不會(huì)執(zhí)行el.value = newValue
,此時(shí)輸入框中的值還是有空格的,所以需要執(zhí)行第四部分的代碼將輸入框中的值替換為trim后的值。
第五部分
我們接著來看第五部分的代碼,如下:
if (!lazy) {
addEventListener(el, "compositionstart", onCompositionStart);
addEventListener(el, "compositionend", onCompositionEnd);
}
如果沒有使用.lazy
修飾符,也就是在每次input時(shí)都會(huì)對(duì)綁定的變量進(jìn)行更新。
這里監(jiān)聽的compositionstart
事件是:文本合成系統(tǒng)如開始新的輸入合成時(shí)會(huì)觸發(fā)?compositionstart
?事件。舉個(gè)例子:當(dāng)用戶使用拼音輸入法開始輸入漢字時(shí),這個(gè)事件就會(huì)被觸發(fā)。
這里監(jiān)聽的compositionend
事件是:當(dāng)文本段落的組成完成或取消時(shí),compositionend 事件將被觸發(fā)。舉個(gè)例子:當(dāng)用戶使用拼音輸入法,將輸入的拼音合成漢字時(shí),這個(gè)事件就會(huì)被觸發(fā)。
來看看onCompositionStart
中的代碼,如下:
function onCompositionStart(e) {
e.target.composing = true;
}
代碼很簡單,將e.target.composing
設(shè)置為true。還記得我們前面在input輸入框的input或者change事件中會(huì)先去判斷這個(gè)e.target.composing
,如果其為true,那么就return掉,這樣就不會(huì)在輸入拼音時(shí)也會(huì)更新v-model綁定的msg
變量了。
我們來看看onCompositionEnd
中的代碼,如下:
function onCompositionEnd(e) {
const target = e.target;
if (target.composing) {
target.composing = false;
target.dispatchEvent(new Event("input"));
}
}
當(dāng)將拼音合成漢字時(shí)會(huì)將e.target.composing
設(shè)置為false,這里為什么要調(diào)用target.dispatchEvent
手動(dòng)觸發(fā)一個(gè)input事件呢?
答案是:將拼音合成漢字時(shí)input事件會(huì)比compositionend事件先觸發(fā),由于此時(shí)的e.target.composing
的值還是true,所以input事件中后續(xù)的代碼就會(huì)被return。所以才需要將e.target.composing
重置為false后,手動(dòng)觸發(fā)一個(gè)input事件,更新v-model綁定的msg
變量。
beforeUpdate鉤子函數(shù)
我們接著來看看beforeUpdate
鉤子函數(shù),會(huì)在每次因?yàn)轫憫?yīng)式狀態(tài)變更,導(dǎo)致頁面更新之前調(diào)用,代碼如下:
const vModelText = {
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el[assignKey] = getModelAssigner(vnode);
// avoid clearing unresolved text. #2302
if (el.composing) return;
const elValue =
number || el.type === "number" ? looseToNumber(el.value) : el.value;
const newValue = value == null ? "" : value;
if (elValue === newValue) {
return;
}
if (document.activeElement === el && el.type !== "range") {
if (lazy) {
return;
}
if (trim && el.value.trim() === newValue) {
return;
}
}
el.value = newValue;
},
};
看完了前面的created
函數(shù),再來看這個(gè)beforeUpdate
函數(shù)就很簡單了。beforeUpdate
鉤子函數(shù)最終要做的事情就是最后的這行代碼:
el.value = newValue;
這行代碼的意思是將輸入框中的值更新成v-model綁定的msg
變量,為什么需要在beforeUpdate
鉤子函數(shù)中執(zhí)行呢?
答案是msg
是一個(gè)響應(yīng)式變量,如果在父組件上面因?yàn)槠渌蚋淖兞?code>msg變量的值后,這個(gè)時(shí)候就需要將input輸入框中的值同步更新為最新的msg變量。這也就解釋了我們前面的問題:如何將vModelText
自定義指令綁定的msg
變量的值傳遞給input輸入框中的value屬性的呢?
第一行代碼是:
el[assignKey] = getModelAssigner(vnode);
這里再次將vnode
上面名為onUpdate:modelValue
的props回調(diào)函數(shù)賦值給el[assignKey]
,之前在created
的時(shí)候不是已經(jīng)賦值過一次了嗎,這里為什么會(huì)再次賦值呢?
答案是在有的場(chǎng)景中是不會(huì)緩存onUpdate:modelValue
回調(diào)函數(shù),如果沒有緩存,那么每次執(zhí)行render函數(shù)都會(huì)生成新的onUpdate:modelValue
回調(diào)函數(shù)。所以才需要在beforeUpdate
鉤子函數(shù)中每次都將最新的onUpdate:modelValue
回調(diào)函數(shù)賦值給el[assignKey]
,當(dāng)在input或者change事件觸發(fā)時(shí)執(zhí)行el[assignKey]
的時(shí)候就是執(zhí)行的最新的onUpdate:modelValue
回調(diào)函數(shù)。
再來看看第二行代碼,如下:
// avoid clearing unresolved text. #2302
if (el.composing) return;
這行代碼是為了修復(fù)bug:如果在輸入拼音的過程中,還沒有合成漢字之前。如果有其他的響應(yīng)式變量的值變化導(dǎo)致頁面刷新,這種時(shí)候就應(yīng)該return。否則由于此時(shí)的msg變量的值還是null,如果執(zhí)行el.value = newValue
,輸入框中的輸入值就會(huì)被清空。詳情請(qǐng)查看issue: https://github.com/vuejs/core/issues/2302
后面的代碼就很簡單了,其中的document.activeElement
屬性返回獲得當(dāng)前焦點(diǎn)(focus)的 DOM 元素,還有type = "range"
我們平時(shí)基本不會(huì)使用。根據(jù)使用的修飾符拿到處理后的input輸入框中的值,然后和v-model綁定的msg
變量進(jìn)行比較。如果兩者相等自然不需要執(zhí)行el.value = newValue
將輸入框中的值更新為最新值。
總結(jié)
現(xiàn)在來看這個(gè)流程圖你應(yīng)該就很容易理解了:
在組件上面使用v-model和原生input上面使用v-model區(qū)別主要有三點(diǎn):
-
組件上面的v-model編譯后會(huì)生成
modelValue
屬性和@update:modelValue
事件。而在原生input上面使用v-model編譯后不會(huì)生成
modelValue
屬性,只會(huì)生成onUpdate:modelValue
回調(diào)函數(shù)和vModelText
自定義指令。(在 面試官:只知道v-model是modelValue語法糖,那你可以走了 文章中我們已經(jīng)講過了@update:modelValue
事件其實(shí)等價(jià)于onUpdate:modelValue
回調(diào)函數(shù)) -
在組件上面使用v-model,是由子組件中定義一個(gè)名為
modelValue
的props來接收父組件使用v-model綁定的變量,然后使用這個(gè)modelValue
綁定到子組件的表單中。在原生input上面使用v-model,是由編譯后生成的
vModelText
自定義指令在mounted
和beforeUpdate
鉤子函數(shù)中去將v-model綁定的變量值更新到原生input輸入框的value屬性,以保證v-model綁定的變量值和input輸入框中的值始終一致。 -
在組件上面使用v-model,是由子組件使用emit拋出
@update:modelValue
事件,在@update:modelValue
的事件處理函數(shù)中去更新v-model綁定的變量。而在原生input上面使用v-model,是由編譯后生成的
vModelText
自定義指令在created
鉤子函數(shù)中去監(jiān)聽原生input標(biāo)簽的input或者change事件。在事件回調(diào)函數(shù)中去手動(dòng)調(diào)用onUpdate:modelValue
回調(diào)函數(shù),然后在回調(diào)函數(shù)中去更新v-model綁定的變量。
關(guān)注公眾號(hào):前端歐陽
,解鎖我更多vue
干貨文章。還可以加我微信,私信我想看哪些vue
原理文章,我會(huì)根據(jù)大家的反饋進(jìn)行創(chuàng)作。文章來源地址http://www.zghlxwxcb.cn/news/detail-856576.html
到了這里,關(guān)于面試官:在原生input上面使用v-model和組件上面使用有什么區(qū)別?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!