自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue 3.0 進(jìn)階之雙向綁定探秘

開發(fā) 前端
本文阿寶哥主要介紹了雙向綁定的概念和 Vue 3 中雙向綁定背后的原理。為了讓大家能夠更深入地掌握 v-model 的相關(guān)知識(shí),阿寶哥從源碼的角度分析了 vModelText 指令的內(nèi)部實(shí)現(xiàn)。

[[382290]]

本文轉(zhuǎn)載自微信公眾號(hào)「全棧修仙之路」,作者阿寶哥。轉(zhuǎn)載本文請(qǐng)聯(lián)系全棧修仙之路公眾號(hào)。  

本文是 Vue 3.0 進(jìn)階系列 的第三篇文章,在閱讀本文前,建議你先閱讀 Vue 3.0 進(jìn)階之指令探秘 和 Vue 3.0 進(jìn)階之自定義事件探秘 這兩篇文章。在看具體示例前,阿寶哥先來簡(jiǎn)單介紹一下雙向綁定,它由兩個(gè)單向綁定組成:

  • 模型 —> 視圖數(shù)據(jù)綁定;
  • 視圖 —> 模型事件綁定。

在 Vue 中 :value 實(shí)現(xiàn)了 模型到視圖 的數(shù)據(jù)綁定,@event 實(shí)現(xiàn)了 視圖到模型 的事件綁定:

  1. <input :value="searchText" @input="searchText = $event.target.value" /> 

而在表單中,通過使用內(nèi)置的 v-model 指令,我們可以輕松地實(shí)現(xiàn)雙向綁定,比如 。介紹完上面的內(nèi)容,接下來阿寶哥將以一個(gè)簡(jiǎn)單的示例為切入點(diǎn),帶大家一起一步步揭開雙向綁定背后的秘密。

  1. <div id="app"
  2.    <input v-model="searchText" /> 
  3.    <p>搜索的內(nèi)容:{{searchText}}</p> 
  4. </div> 
  5. <script> 
  6.    const { createApp } = Vue 
  7.    const app = Vue.createApp({ 
  8.      data() { 
  9.        return { 
  10.          searchText: "阿寶哥" 
  11.        } 
  12.      } 
  13.    }) 
  14.    app.mount('#app'
  15. </script> 

在以上示例中,我們?cè)?input 搜索輸入框中應(yīng)用了 v-model 指令,當(dāng)輸入框的內(nèi)容發(fā)生變化時(shí),p 標(biāo)簽中內(nèi)容會(huì)同步更新。

要揭開 v-model 指令背后的秘密,我們可以利用 Vue 3 Template Explorer 在線工具,來看一下模板編譯后的結(jié)果:

  1. <input v-model="searchText" /> 
  2.  
  3. const _Vue = Vue 
  4. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  5.   with (_ctx) { 
  6.     const { vModelText: _vModelText, createVNode: _createVNode,  
  7.       withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  8.  
  9.     return _withDirectives((_openBlock(), _createBlock("input", { 
  10.       "onUpdate:modelValue": $event => (searchText = $event) 
  11.     }, null, 8 /* PROPS */, ["onUpdate:modelValue"])),  
  12.     [  
  13.       [_vModelText, searchText]  
  14.     ]) 
  15.   } 

在 模板生成的渲染函數(shù)中,我們看到了 Vue 3.0 進(jìn)階之指令探秘 文章中介紹的 withDirectives 函數(shù),該函數(shù)用于把指令信息添加到 VNode 對(duì)象上,它被定義在 runtime-core/src/directives.ts 文件中:

  1. // packages/runtime-core/src/directives.ts 
  2. export function withDirectives<T extends VNode>( 
  3.   vnode: T, 
  4.   directives: DirectiveArguments 
  5. ): T { 
  6.   const internalInstance = currentRenderingInstance 
  7.   // 省略部分代碼 
  8.   const instance = internalInstance.proxy 
  9.   const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = []) 
  10.   for (let i = 0; i < directives.length; i++) { 
  11.     let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i] 
  12.     // 在 mounted 和 updated 時(shí),觸發(fā)相同行為,而不關(guān)系其他的鉤子函數(shù) 
  13.     if (isFunction(dir)) { // 處理函數(shù)類型指令 
  14.       dir = { 
  15.         mounted: dir, 
  16.         updated: dir 
  17.       } as ObjectDirective 
  18.     } 
  19.     bindings.push({ // 把指令信息保存到vnode.dirs數(shù)組中 
  20.       dir, instance, value,  
  21.       oldValue: void 0, arg, modifiers 
  22.     }) 
  23.   } 
  24.   return vnode 

除此之外,在模板生成的渲染函數(shù)中,我們看到了 vModelText 指令,通過它的名稱,我們猜測(cè)該指令與模型相關(guān),所以我們先來分析 vModelText 指令。

一、vModelText 指令

vModelText 指令是 ObjectDirective 類型的指令,該指令中定義了 3 個(gè)鉤子函數(shù):

  • created:在綁定元素的屬性或事件監(jiān)聽器被應(yīng)用之前調(diào)用。
  • mounted:在綁定元素的父組件被掛載后調(diào)用。
  • beforeUpdate:在更新包含組件的 VNode 之前調(diào)用。
  1. // packages/runtime-dom/src/directives/vModel.ts 
  2. type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }> 
  3.  
  4. export const vModelText: ModelDirective< 
  5.   HTMLInputElement | HTMLTextAreaElement 
  6. > = { 
  7.   created(el, { modifiers: { lazy, trim, number } }, vnode) { 
  8.     // ... 
  9.   }, 
  10.   mounted(el, { value }) { 
  11.     // .. 
  12.   }, 
  13.   beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) { 
  14.     // .. 
  15.   } 

接下來,阿寶哥將逐一分析每個(gè)鉤子函數(shù),這里先從 created 鉤子函數(shù)開始。

1.1 created 鉤子

  1. // packages/runtime-dom/src/directives/vModel.ts 
  2. export const vModelText: ModelDirective< 
  3.   HTMLInputElement | HTMLTextAreaElement 
  4. > = { 
  5.   created(el, { modifiers: { lazy, trim, number } }, vnode) { 
  6.     el._assign = getModelAssigner(vnode) 
  7.     const castToNumber = number || el.type === 'number' // 是否轉(zhuǎn)為數(shù)值類型 
  8.     // 若使用 lazy 修飾符,則在 change 事件觸發(fā)后將輸入框的值與數(shù)據(jù)進(jìn)行同步 
  9.     addEventListener(el, lazy ? 'change' : 'input', e => {  
  10.       if ((e.target as any).composing) return // 組合輸入進(jìn)行中 
  11.       let domValue: string | number = el.value 
  12.       if (trim) { // 自動(dòng)過濾用戶輸入的首尾空白字符 
  13.         domValue = domValue.trim() 
  14.       } else if (castToNumber) { // 自動(dòng)將用戶的輸入值轉(zhuǎn)為數(shù)值類型 
  15.         domValue = toNumber(domValue) 
  16.       } 
  17.       el._assign(domValue) // 更新模型 
  18.     }) 
  19.     if (trim) { 
  20.       addEventListener(el, 'change', () => { 
  21.         el.value = el.value.trim() 
  22.       }) 
  23.     } 
  24.     if (!lazy) { 
  25.       addEventListener(el, 'compositionstart', onCompositionStart) 
  26.       addEventListener(el, 'compositionend', onCompositionEnd) 
  27.       // Safari < 10.2 & UIWebView doesn't fire compositionend when 
  28.       // switching focus before confirming composition choice 
  29.       // this also fixes the issue where some browsers e.g. iOS Chrome 
  30.       // fires "change" instead of "input" on autocomplete. 
  31.       addEventListener(el, 'change', onCompositionEnd) 
  32.     } 
  33.   }, 

對(duì)于 created 方法來說,它會(huì)通過解構(gòu)的方式獲取 v-model 指令上添加的修飾符,在 v-model 上可以添加 .lazy、.number 和 .trim 修飾符。這里我們簡(jiǎn)單介紹一下 3 種修飾符的作用:

  • .lazy 修飾符:在默認(rèn)情況下,v-model 在每次 input 事件觸發(fā)后將輸入框的值與數(shù)據(jù)進(jìn)行同步。你可以添加 lazy 修飾符,從而轉(zhuǎn)為在 change 事件之后進(jìn)行同步。
    1. <!-- 在 change 時(shí)而非 input 時(shí)更新 --> 
    2. <input v-model.lazy="msg" /> 
  • .number 修飾符:如果想自動(dòng)將用戶的輸入值轉(zhuǎn)為數(shù)值類型,可以給 v-model 添加 number 修飾符。這通常很有用,因?yàn)榧词乖? type="number" 時(shí),HTML 輸入元素的值也總會(huì)返回字符串。如果這個(gè)值無法被 parseFloat() 解析,則會(huì)返回原始的值。
  1. <input v-model.number="age" type="number" /> 
  • .trim 修飾符:如果要自動(dòng)過濾用戶輸入的首尾空白字符,可以給 v-model 添加 trim 修飾符。
  1. <input v-model.trim="msg" /> 

而在 created 方法內(nèi)部,會(huì)通過 getModelAssigner 函數(shù)獲取 ModelAssigner,從而用于更新模型對(duì)象。

  1. // packages/runtime-dom/src/directives/vModel.ts 
  2. const getModelAssigner = (vnode: VNode): AssignerFn => { 
  3.   const fn = vnode.props!['onUpdate:modelValue'
  4.   return isArray(fn) ? value => invokeArrayFns(fn, value) : fn 

對(duì)于我們的示例來說,通過 getModelAssigner 函數(shù)獲取的 ModelAssigner 對(duì)象是 $event => (searchText = $event) 函數(shù)。在獲取 ModelAssigner 對(duì)象之后,我們就可以更新模型的值了。created 方法中的其他代碼相對(duì)比較簡(jiǎn)單,阿寶哥就不詳細(xì)介紹了。這里我們來介紹一下 compositionstart 和 compositionend 事件。

中文、日文、韓文等需要借助輸入法組合輸入,即使是英文,也可以利用組合輸入進(jìn)行選詞等操作。在一些實(shí)際場(chǎng)景中,我們希望等用戶組合輸入完的一段文字才進(jìn)行對(duì)應(yīng)操作,而不是每輸入一個(gè)字母,就執(zhí)行相關(guān)操作。

比如,在關(guān)鍵字搜索場(chǎng)景中,等用戶完整輸入 阿寶哥 之后再執(zhí)行搜索操作,而不是輸入字母 a 之后就開始搜索。要實(shí)現(xiàn)這個(gè)功能,我們就需要借助 compositionstart 和 compositionend 事件。另外,需要注意的是,compositionstart 事件發(fā)生在 input 事件之前,因此利用它可以優(yōu)化中文輸入的體驗(yàn)。

了解完 compositionstart(組合輸入開始) 和 compositionend (組合輸入結(jié)束)事件,我們?cè)賮砜匆幌? onCompositionStart 和 onCompositionEnd 這兩個(gè)事件處理器:

  1. function onCompositionStart(e: Event) { 
  2.   ;(e.target as any).composing = true 
  3.  
  4. function onCompositionEnd(e: Event) { 
  5.   const target = e.target as any 
  6.   if (target.composing) {  
  7.     target.composing = false 
  8.     trigger(target, 'input'
  9.   } 
  10.  
  11. // 觸發(fā)元素上的指定事件 
  12. function trigger(el: HTMLElement, type: string) { 
  13.   const e = document.createEvent('HTMLEvents'
  14.   e.initEvent(type, truetrue
  15.   el.dispatchEvent(e) 

當(dāng)組合輸入時(shí),在 onCompositionStart 事件處理器中,會(huì) e.target 對(duì)象上添加 composing 屬性并設(shè)置該屬性的值為 true。而在 change 事件或 input 事件回調(diào)函數(shù)中,如果發(fā)現(xiàn) e.target 對(duì)象的 composing 屬性為 true 則會(huì)直接返回。當(dāng)組合輸入完成后,在 onCompositionEnd 事件處理器中,會(huì)把 target.composing 的值設(shè)置為 false 并手動(dòng)觸發(fā) input 事件:

  1. // packages/runtime-dom/src/directives/vModel.ts 
  2. export const vModelText: ModelDirective< 
  3.   HTMLInputElement | HTMLTextAreaElement 
  4. > = { 
  5.   created(el, { modifiers: { lazy, trim, number } }, vnode) { 
  6.     // 省略部分代碼 
  7.     addEventListener(el, lazy ? 'change' : 'input', e => { 
  8.       if ((e.target as any).composing) return 
  9.      // ... 
  10.     }) 
  11.   }, 

好的,created 鉤子函數(shù)就分析到這里,接下來我們來分析 mounted 鉤子。

1.2 mounted 鉤子

  1. // packages/runtime-dom/src/directives/vModel.ts 
  2. export const vModelText: ModelDirective< 
  3.   HTMLInputElement | HTMLTextAreaElement 
  4. > = { 
  5.   // set value on mounted so it's after min/max for type="range" 
  6.   mounted(el, { value }) { 
  7.     el.value = value == null ? '' : value 
  8.   }, 

mounted 鉤子的邏輯很簡(jiǎn)單,如果 value 值為 null 時(shí),把元素的值設(shè)置為空字符串,否則直接使用 value 的值。

1.3 beforeUpdate 鉤子

  1. // packages/runtime-dom/src/directives/vModel.ts 
  2. export const vModelText: ModelDirective< 
  3.   HTMLInputElement | HTMLTextAreaElement 
  4. > = { 
  5.   beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) { 
  6.     el._assign = getModelAssigner(vnode) 
  7.     // avoid clearing unresolved text. #2302 
  8.     if ((el as any).composing) return 
  9.     if (document.activeElement === el) { 
  10.       if (trim && el.value.trim() === value) { 
  11.         return 
  12.       } 
  13.       if ((number || el.type === 'number') && toNumber(el.value) === value) { 
  14.         return 
  15.       } 
  16.     } 
  17.     const newValue = value == null ? '' : value 
  18.     if (el.value !== newValue) { // 新舊值不相等時(shí),執(zhí)行更新操作 
  19.       el.value = newValue 
  20.     } 
  21.   } 

相信使用過 Vue 的小伙伴都知道,v-model 指令不僅可以應(yīng)用在 input 和 textarea 元素上,在復(fù)選框(Checkbox)、單選框(Radio)和選擇框(Select)上也可以使用 v-model 指令。不過需要注意的是,雖然這些元素上都是使用 v-model 指令,但實(shí)際上對(duì)于復(fù)選框、單選框和選擇框來說,它們是由不同的指令來完成對(duì)應(yīng)的功能。這里我們以單選框?yàn)槔?,來看一下?yīng)用 v-model 指令后,模板編譯的結(jié)果:

  1. <input type="radio" value="One" v-model="picked" /> 
  2.  
  3. const _Vue = Vue 
  4. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  5.   with (_ctx) { 
  6.     const { vModelRadio: _vModelRadio, createVNode: _createVNode,  
  7.       withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  8.  
  9.     return _withDirectives((_openBlock(), _createBlock("input", { 
  10.       type: "radio"
  11.       value: "One"
  12.       "onUpdate:modelValue": $event => (picked = $event) 
  13.     }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [ 
  14.       [_vModelRadio, picked] 
  15.     ]) 
  16.   } 

由以上代碼可知,在單選框應(yīng)用 v-model 指令后,雙向綁定的功能會(huì)交給 vModelRadio 指令來實(shí)現(xiàn)。除了 vModelRadio 之外,還有 vModelSelect 和 vModelCheckbox 指令,它們被定義在 runtime-dom/src/directives/vModel.ts 文件中,感興趣的小伙伴可以自行研究一下。

其實(shí) v-model 本質(zhì)上是語法糖。它負(fù)責(zé)監(jiān)聽用戶的輸入事件來更新數(shù)據(jù),并在某些場(chǎng)景下進(jìn)行一些特殊處理。需要注意的是 v-model 會(huì)忽略所有表單元素的 value、checked、selected attribute 的初始值而總是將當(dāng)前活動(dòng)實(shí)例的數(shù)據(jù)作為數(shù)據(jù)來源。你應(yīng)該通過在組件的 data 選項(xiàng)中聲明初始值。

此外,v-model 在內(nèi)部為不同的輸入元素使用不同的 property 并拋出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 元素使用 check property 和 change 事件;
  • select 元素將 value 作為 prop 并將 change 作為事件。

這里你已經(jīng)知道,可以用 v-model 指令在表單 <input>、<textarea> 及 <select> 元素上創(chuàng)建雙向數(shù)據(jù)綁定。但如果你也想在組件上使用 v-model 指令來創(chuàng)建雙向數(shù)據(jù)綁定,那應(yīng)該如何實(shí)現(xiàn)呢?

二、在組件上使用 v-model

假設(shè)你想定義一個(gè) custom-input 組件并在該組件上使用 v-model 指令來實(shí)現(xiàn)雙向綁定,在實(shí)現(xiàn)該功能前,我們先利用 Vue 3 Template Explorer 在線工具,看一下模板編譯后的結(jié)果:

  1. <custom-input v-model="searchText"></custom-input> 
  2.  
  3. const _Vue = Vue 
  4. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  5.   with (_ctx) { 
  6.     const { resolveComponent: _resolveComponent, createVNode: _createVNode,  
  7.       openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  8.  
  9.     const _component_custom_input = _resolveComponent("custom-input"
  10.     return (_openBlock(), _createBlock(_component_custom_input, { 
  11.       modelValue: searchText, 
  12.       "onUpdate:modelValue": $event => (searchText = $event) 
  13.     }, null, 8 /* PROPS */, ["modelValue""onUpdate:modelValue"])) 
  14.   } 

通過觀察以上的渲染函數(shù),我們可知在 custom-input 組件上應(yīng)用了 v-model 指令,經(jīng)過編譯器編譯之后,會(huì)生成一個(gè)名為 modelValue 的輸入屬性和一個(gè)名為 update:modelValue 的自定義事件名。如果你對(duì)自定義事件內(nèi)部原理還不清楚的話,可以閱讀 Vue 3.0 進(jìn)階之自定義事件探秘 這篇文章。了解完這些內(nèi)容之后,我們就可以開始實(shí)現(xiàn) custom-input 組件了:

  1. <div id="app"
  2.    <custom-input v-model="searchText"></custom-input> 
  3.    <p>搜索的內(nèi)容:{{searchText}}</p> 
  4. </div> 
  5. <script> 
  6.    const { createApp } = Vue 
  7.    const app = Vue.createApp({ 
  8.      data() { 
  9.        return { 
  10.          searchText: "阿寶哥" 
  11.        } 
  12.      } 
  13.     }) 
  14.    app.component('custom-input', { 
  15.      props: ['modelValue'], 
  16.      emits: ['update:modelValue'], 
  17.      template: ` 
  18.        <input type="text"  
  19.           :value="modelValue" 
  20.           @input="$emit('update:modelValue', $event.target.value)" 
  21.        >` 
  22.    }) 
  23.    app.mount('#app'
  24. </script> 

在自定義組件中實(shí)現(xiàn)雙向綁定的功能,除了使用自定義事件之外,還可以使用計(jì)算屬性的功能來定義 getter 和 setter。這里阿寶哥就不展開介紹了,感興趣的小伙伴可以閱讀 Vue 3 官網(wǎng) - 組件基礎(chǔ) 的相關(guān)內(nèi)容。

三、阿寶哥有話說

3.1 如何修改 v-model 默認(rèn)的 prop 名和事件名?

默認(rèn)情況下,組件上的 v-model 使用 modelValue 作為 prop 和 update:modelValue 作為事件。我們可以通過向 v-model 指令傳遞參數(shù)來修改這些名稱:

  1. <custom-input v-model:name="searchText"></custom-input> 

以上的模板,經(jīng)過編譯器編譯后的結(jié)果如下:

  1. const _Vue = Vue 
  2. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  3.   with (_ctx) { 
  4.     const { resolveComponent: _resolveComponent, createVNode: _createVNode,  
  5.       openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  6.  
  7.     const _component_custom_input = _resolveComponent("custom-input"
  8.     return (_openBlock(), _createBlock(_component_custom_input, { 
  9.       name: searchText, 
  10.       "onUpdate:name": $event => (searchText = $event) 
  11.     }, null, 8 /* PROPS */, ["name""onUpdate:name"])) 
  12.   } 

通過觀察生成的渲染函數(shù),我們可知自定義 custom-input 組件接收一個(gè) name 輸入屬性并含有一個(gè)名為 update:name 的自定義事件:

  1. app.component('custom-input', { 
  2.   props: { 
  3.     name: String 
  4.   }, 
  5.   emits: ['update:name'], 
  6.   template: ` 
  7.     <input type="text" 
  8.       :value="name" 
  9.       @input="$emit('update:name', $event.target.value)"
  10.   ` 
  11. }) 

至于自定義的事件名為什么是 "onUpdate:name" 這種形式,你可以從 Vue 3.0 進(jìn)階之自定義事件探秘 這篇文章中介紹的 emit 函數(shù)中找到對(duì)應(yīng)的答案。

3.2 能否在組件上使用多個(gè) v-model 指令?

在某些場(chǎng)景下,我們是希望在組件上使用多個(gè) v-model 指令,每個(gè)指令與不同的數(shù)據(jù)做綁定。比如一個(gè) user-name 組件,該組件允許用戶輸入 firstName 和 lastName。該組件期望的使用方式如下:

  1. <user-name 
  2.   v-model:first-name="firstName" 
  3.   v-model:last-name="lastName" 
  4. ></user-name

同樣,我們使用 Vue 3 Template Explorer 在線工具,先來看一下以上模板編譯后的結(jié)果:

  1. const _Vue = Vue 
  2. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  3.   with (_ctx) { 
  4.     const { resolveComponent: _resolveComponent, createVNode: _createVNode,  
  5.       openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  6.  
  7.     const _component_user_name = _resolveComponent("user-name"
  8.     return (_openBlock(), _createBlock(_component_user_name, { 
  9.       "first-name": firstName, 
  10.       "onUpdate:first-name": $event => (firstName = $event), 
  11.       "last-name": lastName, 
  12.       "onUpdate:last-name": $event => (lastName = $event) 
  13.     }, null, 8 /* PROPS */, ["first-name""onUpdate:first-name""last-name""onUpdate:last-name"])) 
  14.   } 

通過觀察以上的輸出結(jié)果,我們可知 v-model:first-name 和 v-model:last-name 都會(huì)生成對(duì)應(yīng)的 prop 屬性和自定義事件。HTML 中的 attribute 名是大小寫不敏感的,所以瀏覽器會(huì)把所有大寫字符解釋為小寫字符。這意味著當(dāng)你使用 DOM 中的模板時(shí),camelCase (駝峰命名法)的 prop 名需要使用其等價(jià)的 kebab-case(短橫線分隔命名)命名。比如:

  1. <!-- kebab-case in HTML --> 
  2. <blog-post post-title="hello!"></blog-post> 
  3.  
  4. app.component('blog-post', { 
  5.   props: ['postTitle'], 
  6.   template: '<h3>{{ postTitle }}</h3>' 
  7. }) 

反之,對(duì)于 first-name 和 last-name 屬性名來說,在定義 user-name 組件時(shí),我們將使用 firstName 和 lastName 駝峰命名方式。

  1.  <div id="app"
  2.     <user-name 
  3.        v-model:first-name="firstName" 
  4.        v-model:last-name="lastName"
  5.     </user-name
  6.     Your name: {{firstName}} {{lastName}} 
  7. </div> 
  8. <script> 
  9.    const { createApp } = Vue 
  10.    const app = Vue.createApp({ 
  11.      data() { 
  12.        return { 
  13.          firstName: ""
  14.          lastName: "" 
  15.        } 
  16.      } 
  17.    }) 
  18.    app.component('user-name', { 
  19.      props: { 
  20.        firstName: String, 
  21.        lastName: String 
  22.      }, 
  23.      emits: ['update:firstName''update:lastName'], 
  24.      template: ` 
  25.        <input 
  26.           type="text" 
  27.           :value="firstName" 
  28.           @input="$emit('update:firstName', $event.target.value)"
  29.        <input 
  30.           type="text" 
  31.           :value="lastName" 
  32.           @input="$emit('update:lastName', $event.target.value)"
  33.       ` 
  34.    }) 
  35.    app.mount('#app'
  36. </script> 

在以上的代碼中,user-name 組件使用的自定義屬性和事件名都是駝峰的形式。很明顯與模板編譯后生成的命名格式不一致,那么以上的 user-name 組件可以正常工作么?答案是可以的,這是因?yàn)閷?duì)于自定義事件來說,在 emit 函數(shù)內(nèi)部會(huì)通過 hyphenate 函數(shù),把事件名從 camelCase(駝峰命名法)的形式轉(zhuǎn)換為 kebab-case(短橫線分隔命名)的形式,即 hyphenate(event):

  1. // packages/runtime-core/src/componentEmits.ts 
  2. export function emit( 
  3.   instance: ComponentInternalInstance, 
  4.   event: string, 
  5.   ...rawArgs: any[] 
  6. ) { 
  7.   // 省略部分代碼 
  8.   // for v-model update:xxx events, also trigger kebab-case equivalent 
  9.   // for props passed via kebab-case 
  10.   if (!handler && isModelListener) { 
  11.     handlerName = toHandlerKey(hyphenate(event)) 
  12.     handler = props[handlerName] 
  13.   } 
  14.  
  15.   if (handler) { 
  16.     callWithAsyncErrorHandling( 
  17.       handler, 
  18.       instance, 
  19.       ErrorCodes.COMPONENT_EVENT_HANDLER, 
  20.       args 
  21.     ) 
  22.   } 

而 hyphenate 函數(shù)的實(shí)現(xiàn)也很簡(jiǎn)單,具體如下所示:

  1. // packages/shared/src/index.ts 
  2. const hyphenateRE = /\B([A-Z])/g 
  3.  
  4. // cacheStringFunction 函數(shù)提供了緩存功能 
  5. export const hyphenate = cacheStringFunction((str: string) => 
  6.   str.replace(hyphenateRE, '-$1').toLowerCase() 

3.3 如何為 v-model 添加自定義修飾符?

在前面阿寶哥已經(jīng)介紹了 v-model 的內(nèi)置修飾符:.trim、.number 和 .lazy。但在某些場(chǎng)景下,你可能希望自定義修飾符。在介紹如何自定義修飾符前,我們?cè)俅卫?Vue 3 Template Explorer 在線工具,看一下 v-model 使用內(nèi)置修飾符后,模板編譯的結(jié)果:

  1. <custom-input v-model.lazy.number="searchText"></custom-input> 
  2.  
  3. const _Vue = Vue 
  4. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  5.   with (_ctx) { 
  6.     const { resolveComponent: _resolveComponent, createVNode: _createVNode,  
  7.       openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  8.  
  9.     const _component_custom_input = _resolveComponent("custom-input"
  10.     return (_openBlock(), _createBlock(_component_custom_input, { 
  11.       modelValue: searchText, 
  12.       "onUpdate:modelValue": $event => (searchText = $event), 
  13.       modelModifiers: { lazy: true, number: true } 
  14.     }, null, 8 /* PROPS */, ["modelValue""onUpdate:modelValue"])) 
  15.   } 

通過觀察生成的渲染函數(shù),我們可以看到 v-model 上添加的 .lazy 和 .number 修飾符,被編譯到 modelModifiers prop 屬性中。假設(shè)我們要為自定義一個(gè) capitalize 修飾符 ,該修飾符的作用是將 v-model 綁定字符串的第一個(gè)字母轉(zhuǎn)成大寫:

  1. <custom-input v-model.capitalize="searchText"></custom-input> 
  2.  
  3. const _Vue = Vue 
  4. return function render(_ctx, _cache, $props, $setup, $data, $options) { 
  5.   with (_ctx) { 
  6.     const { resolveComponent: _resolveComponent, createVNode: _createVNode,  
  7.       openBlock: _openBlock, createBlock: _createBlock } = _Vue 
  8.  
  9.     const _component_custom_input = _resolveComponent("custom-input"
  10.     return (_openBlock(), _createBlock(_component_custom_input, { 
  11.       modelValue: searchText, 
  12.       "onUpdate:modelValue": $event => (searchText = $event), 
  13.       modelModifiers: { capitalize: true } 
  14.     }, null, 8 /* PROPS */, ["modelValue""onUpdate:modelValue"])) 
  15.   } 

很明顯 v-model 上的 .capitalize 修飾符,也被編譯到 modelModifiers prop 屬性中。了解完這些,我們就可以實(shí)現(xiàn)上述的修飾符,具體如下所示:

  1. <div id="app"
  2.    <custom-input v-model.capitalize="searchText"></custom-input> 
  3.    <p>搜索的內(nèi)容:{{searchText}}</p> 
  4. </div> 
  5. <script> 
  6.    const { createApp } = Vue 
  7.    const app = Vue.createApp({ 
  8.      data() { 
  9.        return { 
  10.          searchText: "" 
  11.        } 
  12.      } 
  13.    }) 
  14.    app.component('custom-input', { 
  15.      props: { 
  16.        modelValue: String, 
  17.        modelModifiers: { 
  18.          default: () => ({}) 
  19.        } 
  20.      }, 
  21.      emits: ['update:modelValue'], 
  22.      methods: { 
  23.        emitValue(e) { 
  24.          let value = e.target.value 
  25.          if (this.modelModifiers.capitalize) { 
  26.            value = value.charAt(0).toUpperCase() + value.slice(1) 
  27.          } 
  28.          this.$emit('update:modelValue', value) 
  29.        } 
  30.      }, 
  31.      template: `<input 
  32.        type="text" 
  33.        :value="modelValue" 
  34.        @input="emitValue">` 
  35.    }) 
  36.   app.mount('#app'
  37. </script> 

本文阿寶哥主要介紹了雙向綁定的概念和 Vue 3 中雙向綁定背后的原理。為了讓大家能夠更深入地掌握 v-model 的相關(guān)知識(shí),阿寶哥從源碼的角度分析了 vModelText 指令的內(nèi)部實(shí)現(xiàn)。

此外,阿寶哥還介紹了在組件中如何使用多個(gè) v-model 指令及如何為 v-model 添加自定義修飾符。Vue 3.0 進(jìn)階系列的文章還在持續(xù)更新,感興趣的小伙伴請(qǐng)持續(xù)關(guān)注喲。

四、參考資源

Vue 3 官網(wǎng) - 自定義指令

Vue 3 官網(wǎng) - 自定義事件

 

責(zé)任編輯:武曉燕 來源: 全棧修仙之路
相關(guān)推薦

2021-02-16 16:41:45

Vue項(xiàng)目指令

2021-02-26 05:19:20

Vue 3.0 VNode虛擬

2021-02-22 21:49:33

Vue動(dòng)態(tài)組件

2021-02-28 20:41:18

Vue注入Angular

2021-02-18 08:19:21

Vue自定義Vue 3.0

2021-03-04 22:31:02

Vue進(jìn)階函數(shù)

2017-08-08 09:15:41

前端JavaScript頁(yè)面渲染

2021-03-08 00:08:29

Vue應(yīng)用掛載

2021-03-09 22:29:46

Vue 響應(yīng)式API

2022-08-22 09:01:24

Vue響應(yīng)式原則雙向數(shù)據(jù)綁定

2021-09-13 09:20:20

前端框架VUE

2021-04-02 11:24:22

Vue2.x雙向綁定前端

2016-12-27 15:23:56

vue.js雙向綁定操作

2010-05-11 16:22:40

2022-09-02 10:34:23

數(shù)據(jù)Vue

2021-09-01 14:36:14

鴻蒙HarmonyOS應(yīng)用

2017-10-27 22:03:35

javascrip

2020-10-13 08:24:31

Vue3.0系列

2021-05-07 08:20:52

前端開發(fā)技術(shù)熱點(diǎn)

2020-04-22 14:15:32

Vue 3.0語法前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)