中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue 3.0 中怎么實現雙向綁定

發布時間:2021-07-09 11:14:47 來源:億速云 閱讀:362 作者:Leah 欄目:web開發

本篇文章為大家展示了Vue 3.0 中怎么實現雙向綁定,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

雙向綁定由兩個單向綁定組成:

  • 模型 —> 視圖數據綁定;

  • 視圖 —> 模型事件綁定。

Vue 3.0 中怎么實現雙向綁定

在 Vue 中 :value 實現了 模型到視圖 的數據綁定,@event 實現了 視圖到模型 的事件綁定:

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

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

<div id="app">    <input v-model="searchText" />    <p>搜索的內容:{{searchText}}</p> </div> <script>    const { createApp } = Vue    const app = Vue.createApp({      data() {        return {          searchText: "阿寶哥"        }      }    })    app.mount('#app') </script>

在以上示例中,我們在 input 搜索輸入框中應用了 v-model 指令,當輸入框的內容發生變化時,p 標簽中內容會同步更新。

Vue 3.0 中怎么實現雙向綁定

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

<input v-model="searchText" />  const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { vModelText: _vModelText, createVNode: _createVNode,        withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue      return _withDirectives((_openBlock(), _createBlock("input", {       "onUpdate:modelValue": $event => (searchText = $event)     }, null, 8 /* PROPS */, ["onUpdate:modelValue"])),      [        [_vModelText, searchText]      ])   } }

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

// packages/runtime-core/src/directives.ts export function withDirectives<T extends VNode>(   vnode: T,   directives: DirectiveArguments ): T {   const internalInstance = currentRenderingInstance   // 省略部分代碼   const instance = internalInstance.proxy   const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])   for (let i = 0; i < directives.length; i++) {     let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]     // 在 mounted 和 updated 時,觸發相同行為,而不關系其他的鉤子函數     if (isFunction(dir)) { // 處理函數類型指令       dir = {         mounted: dir,         updated: dir       } as ObjectDirective     }     bindings.push({ // 把指令信息保存到vnode.dirs數組中       dir, instance, value,        oldValue: void 0, arg, modifiers     })   }   return vnode }

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

一、vModelText 指令

vModelText 指令是 ObjectDirective 類型的指令,該指令中定義了 3 個鉤子函數:

  • created:在綁定元素的屬性或事件監聽器被應用之前調用。

  • mounted:在綁定元素的父組件被掛載后調用。

  • beforeUpdate:在更新包含組件的 VNode 之前調用。

// packages/runtime-dom/src/directives/vModel.ts type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>  export const vModelText: ModelDirective<   HTMLInputElement | HTMLTextAreaElement > = {   created(el, { modifiers: { lazy, trim, number } }, vnode) {     // ...   },   mounted(el, { value }) {     // ..   },   beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {     // ..   } }

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

1.1 created 鉤子

// packages/runtime-dom/src/directives/vModel.ts export const vModelText: ModelDirective<   HTMLInputElement | HTMLTextAreaElement > = {   created(el, { modifiers: { lazy, trim, number } }, vnode) {     el._assign = getModelAssigner(vnode)     const castToNumber = number || el.type === 'number' // 是否轉為數值類型     // 若使用 lazy 修飾符,則在 change 事件觸發后將輸入框的值與數據進行同步     addEventListener(el, lazy ? 'change' : 'input', e => {        if ((e.target as any).composing) return // 組合輸入進行中       let domValue: string | number = el.value       if (trim) { // 自動過濾用戶輸入的首尾空白字符         domValue = domValue.trim()       } else if (castToNumber) { // 自動將用戶的輸入值轉為數值類型         domValue = toNumber(domValue)       }       el._assign(domValue) // 更新模型     })     if (trim) {       addEventListener(el, 'change', () => {         el.value = el.value.trim()       })     }     if (!lazy) {       addEventListener(el, 'compositionstart', onCompositionStart)       addEventListener(el, 'compositionend', onCompositionEnd)       // Safari < 10.2 & UIWebView doesn't fire compositionend when       // switching focus before confirming composition choice       // this also fixes the issue where some browsers e.g. iOS Chrome       // fires "change" instead of "input" on autocomplete.       addEventListener(el, 'change', onCompositionEnd)     }   }, }

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

  • .lazy 修飾符:在默認情況下,v-model 在每次 input 事件觸發后將輸入框的值與數據進行同步。你可以添加 lazy 修飾符,從而轉為在  change 事件之后進行同步。

  • <!-- 在 change 時而非 input 時更新 --> <input v-model.lazy="msg" />
  • .number 修飾符:如果想自動將用戶的輸入值轉為數值類型,可以給 v-model 添加 number 修飾符。這通常很有用,因為即使在  type="number" 時,HTML 輸入元素的值也總會返回字符串。如果這個值無法被 parseFloat() 解析,則會返回原始的值。

<input v-model.number="age" type="number" />
  • .trim 修飾符:如果要自動過濾用戶輸入的首尾空白字符,可以給 v-model 添加 trim 修飾符。

<input v-model.trim="msg" />

而在 created 方法內部,會通過 getModelAssigner 函數獲取 ModelAssigner,從而用于更新模型對象。

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

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

中文、日文、韓文等需要借助輸入法組合輸入,即使是英文,也可以利用組合輸入進行選詞等操作。在一些實際場景中,我們希望等用戶組合輸入完的一段文字才進行對應操作,而不是每輸入一個字母,就執行相關操作。

比如,在關鍵字搜索場景中,等用戶完整輸入 阿寶哥 之后再執行搜索操作,而不是輸入字母 a 之后就開始搜索。要實現這個功能,我們就需要借助  compositionstart 和 compositionend 事件。另外,需要注意的是,compositionstart 事件發生在 input  事件之前,因此利用它可以優化中文輸入的體驗。

了解完 compositionstart(組合輸入開始) 和 compositionend (組合輸入結束)事件,我們再來看一下  onCompositionStart 和 onCompositionEnd 這兩個事件處理器:

function onCompositionStart(e: Event) {   ;(e.target as any).composing = true }  function onCompositionEnd(e: Event) {   const target = e.target as any   if (target.composing) {      target.composing = false     trigger(target, 'input')   } }  // 觸發元素上的指定事件 function trigger(el: HTMLElement, type: string) {   const e = document.createEvent('HTMLEvents')   e.initEvent(type, true, true)   el.dispatchEvent(e) }

當組合輸入時,在 onCompositionStart 事件處理器中,會 e.target 對象上添加 composing 屬性并設置該屬性的值為  true。而在 change 事件或 input 事件回調函數中,如果發現 e.target 對象的 composing 屬性為 true  則會直接返回。當組合輸入完成后,在 onCompositionEnd 事件處理器中,會把 target.composing 的值設置為 false 并手動觸發  input 事件:

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

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

1.2 mounted 鉤子

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

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

1.3 beforeUpdate 鉤子

// packages/runtime-dom/src/directives/vModel.ts export const vModelText: ModelDirective<   HTMLInputElement | HTMLTextAreaElement > = {   beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {     el._assign = getModelAssigner(vnode)     // avoid clearing unresolved text. #2302     if ((el as any).composing) return     if (document.activeElement === el) {       if (trim && el.value.trim() === value) {         return       }       if ((number || el.type === 'number') && toNumber(el.value) === value) {         return       }     }     const newValue = value == null ? '' : value     if (el.value !== newValue) { // 新舊值不相等時,執行更新操作       el.value = newValue     }   } }

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

<input type="radio" value="One" v-model="picked" />  const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { vModelRadio: _vModelRadio, createVNode: _createVNode,        withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue      return _withDirectives((_openBlock(), _createBlock("input", {       type: "radio",       value: "One",       "onUpdate:modelValue": $event => (picked = $event)     }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [       [_vModelRadio, picked]     ])   } }

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

其實 v-model 本質上是語法糖。它負責監聽用戶的輸入事件來更新數據,并在某些場景下進行一些特殊處理。需要注意的是 v-model  會忽略所有表單元素的 value、checked、selected attribute 的初始值而總是將當前活動實例的數據作為數據來源。你應該通過在組件的  data 選項中聲明初始值。

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

  • text 和 textarea 元素使用 value property 和 input 事件;

  • checkbox 和 radio 元素使用 check property 和 change 事件;

  • select 元素將 value 作為 prop 并將 change 作為事件。

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

二、在組件上使用 v-model

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

<custom-input v-model="searchText"></custom-input>  const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { resolveComponent: _resolveComponent, createVNode: _createVNode,        openBlock: _openBlock, createBlock: _createBlock } = _Vue      const _component_custom_input = _resolveComponent("custom-input")     return (_openBlock(), _createBlock(_component_custom_input, {       modelValue: searchText,       "onUpdate:modelValue": $event => (searchText = $event)     }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))   } }

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

<div id="app">    <custom-input v-model="searchText"></custom-input>    <p>搜索的內容:{{searchText}}</p> </div> <script>    const { createApp } = Vue    const app = Vue.createApp({      data() {        return {          searchText: "阿寶哥"        }      }     })    app.component('custom-input', {      props: ['modelValue'],      emits: ['update:modelValue'],      template: `        <input type="text"            :value="modelValue"           @input="$emit('update:modelValue', $event.target.value)"        >`    })    app.mount('#app') </script>

在自定義組件中實現雙向綁定的功能,除了使用自定義事件之外,還可以使用計算屬性的功能來定義 getter 和 setter。這里阿寶哥就不展開介紹了,感興趣的小伙伴可以閱讀 Vue 3 官網 - 組件基礎 的相關內容。

三、阿寶哥有話說

3.1 如何修改 v-model 默認的 prop 名和事件名?

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

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

以上的模板,經過編譯器編譯后的結果如下:

const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { resolveComponent: _resolveComponent, createVNode: _createVNode,        openBlock: _openBlock, createBlock: _createBlock } = _Vue      const _component_custom_input = _resolveComponent("custom-input")     return (_openBlock(), _createBlock(_component_custom_input, {       name: searchText,       "onUpdate:name": $event => (searchText = $event)     }, null, 8 /* PROPS */, ["name", "onUpdate:name"]))   } }

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

app.component('custom-input', {   props: {     name: String   },   emits: ['update:name'],   template: `     <input type="text"       :value="name"       @input="$emit('update:name', $event.target.value)">   ` })

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

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

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

<user-name   v-model:first-name="firstName"   v-model:last-name="lastName" ></user-name>

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

const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { resolveComponent: _resolveComponent, createVNode: _createVNode,        openBlock: _openBlock, createBlock: _createBlock } = _Vue      const _component_user_name = _resolveComponent("user-name")     return (_openBlock(), _createBlock(_component_user_name, {       "first-name": firstName,       "onUpdate:first-name": $event => (firstName = $event),       "last-name": lastName,       "onUpdate:last-name": $event => (lastName = $event)     }, null, 8 /* PROPS */, ["first-name", "onUpdate:first-name", "last-name", "onUpdate:last-name"]))   } }

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

<!-- kebab-case in HTML --> <blog-post post-title="hello!"></blog-post>  app.component('blog-post', {   props: ['postTitle'],   template: '<h4>{{ postTitle }}</h4>' })

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

 <div id="app">     <user-name        v-model:first-name="firstName"        v-model:last-name="lastName">     </user-name>     Your name: {{firstName}} {{lastName}} </div> <script>    const { createApp } = Vue    const app = Vue.createApp({      data() {        return {          firstName: "",          lastName: ""        }      }    })    app.component('user-name', {      props: {        firstName: String,        lastName: String      },      emits: ['update:firstName', 'update:lastName'],      template: `        <input           type="text"           :value="firstName"           @input="$emit('update:firstName', $event.target.value)">        <input           type="text"           :value="lastName"           @input="$emit('update:lastName', $event.target.value)">       `    })    app.mount('#app') </script>

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

// packages/runtime-core/src/componentEmits.ts export function emit(   instance: ComponentInternalInstance,   event: string,   ...rawArgs: any[] ) {   // 省略部分代碼   // for v-model update:xxx events, also trigger kebab-case equivalent   // for props passed via kebab-case   if (!handler && isModelListener) {     handlerName = toHandlerKey(hyphenate(event))     handler = props[handlerName]   }    if (handler) {     callWithAsyncErrorHandling(       handler,       instance,       ErrorCodes.COMPONENT_EVENT_HANDLER,       args     )   } }

而 hyphenate 函數的實現也很簡單,具體如下所示:

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

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

在前面阿寶哥已經介紹了 v-model 的內置修飾符:.trim、.number 和 .lazy。但在某些場景下,你可能希望自定義修飾符。在介紹如何自定義修飾符前,我們再次利用 Vue 3 Template Explorer 在線工具,看一下 v-model 使用內置修飾符后,模板編譯的結果:

<custom-input v-model.lazy.number="searchText"></custom-input>  const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { resolveComponent: _resolveComponent, createVNode: _createVNode,        openBlock: _openBlock, createBlock: _createBlock } = _Vue      const _component_custom_input = _resolveComponent("custom-input")     return (_openBlock(), _createBlock(_component_custom_input, {       modelValue: searchText,       "onUpdate:modelValue": $event => (searchText = $event),       modelModifiers: { lazy: true, number: true }     }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))   } }

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

<custom-input v-model.capitalize="searchText"></custom-input>  const _Vue = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) {   with (_ctx) {     const { resolveComponent: _resolveComponent, createVNode: _createVNode,        openBlock: _openBlock, createBlock: _createBlock } = _Vue      const _component_custom_input = _resolveComponent("custom-input")     return (_openBlock(), _createBlock(_component_custom_input, {       modelValue: searchText,       "onUpdate:modelValue": $event => (searchText = $event),       modelModifiers: { capitalize: true }     }, null, 8 /* PROPS */, ["modelValue", "onUpdate:modelValue"]))   } }

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

<div id="app">    <custom-input v-model.capitalize="searchText"></custom-input>    <p>搜索的內容:{{searchText}}</p> </div> <script>    const { createApp } = Vue    const app = Vue.createApp({      data() {        return {          searchText: ""        }      }    })    app.component('custom-input', {      props: {        modelValue: String,        modelModifiers: {          default: () => ({})        }      },      emits: ['update:modelValue'],      methods: {        emitValue(e) {          let value = e.target.value          if (this.modelModifiers.capitalize) {            value = value.charAt(0).toUpperCase() + value.slice(1)          }          this.$emit('update:modelValue', value)        }      },      template: `<input        type="text"        :value="modelValue"        @input="emitValue">`    })   app.mount('#app') </script>

上述內容就是Vue 3.0 中怎么實現雙向綁定,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

vue
AI

屯昌县| 沈丘县| 施甸县| 东乌| 车险| 武清区| 康马县| 宜良县| 周至县| 民和| 阳曲县| 山西省| 攀枝花市| 三门县| 若羌县| 建始县| 凌源市| 开平市| 鞍山市| 祁阳县| 南涧| 勃利县| 革吉县| 承德县| 金门县| 德庆县| 台东县| 宣化县| 栾城县| 娄烦县| 灵武市| 同德县| 洛隆县| 夏邑县| 青浦区| 兴城市| 米脂县| 华池县| 佛山市| 射阳县| 常宁市|