【快速入門Vue系列】第十二篇:非Prop特性和監聽子元件的事件

語言: CN / TW / HK

元件_非Prop特性

非Prop特性指的是,一個未被元件註冊的特性。當元件接收了一個非Prop特性時,該特性會被新增到這個元件的根元素上。

替換/合併已有的特性

想象一下 <my-cmp> 的模板是這樣的:

html <input type="date" class="b">

為了給我們的日期選擇器外掛定製一個主題,我們可能需要像這樣新增一個特別的類名:

```html <my-cmp class="my-cmp"

```

在這種情況下,我們定義了兩個不同的 class 的值:

  • my-cmp,這是在元件的模板內設定好的
  • b,這是從元件的父級傳入的

對於絕大多數特性來說,從外部提供給元件的值會替換掉元件內部設定好的值。所以如果傳入 type="text" 就會替換掉 type="date" 並把它破壞!慶幸的是,class 和 style 特性會稍微智慧一些,即兩邊的值會被合併起來,從而得到最終的值:my-cmp b。

禁用特性繼承

如果不希望元件的根元素繼承特性,那麼可以在元件選項中設定 inheritAttrs: false。如:

js Vue.component('my-cmp', { inheritAttrs: false, // ... })

在這種情況下,非常適合去配合例項的 $attrs 屬性使用,這個屬性是一個物件,鍵名為傳遞的特性名,鍵值為傳遞特性值。

js { required: true, placeholder: 'Enter your username' }

使用 inheritAttrs: false 和 $attrs 相互配合,我們就可以手動決定這些特性會被賦予哪個元素。如:

js Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > </label> `, })

注意:inheritAttrs: false 選項不會影響 style 和 class 的繫結。

元件_監聽元件事件

首先,我們來寫一個博文元件,如:

js Vue.component('blog-post', { props: { post: { type: Object, } }, template: ` <div class="blog-post"> <h3>{{ post.title }}</h3> <button>放大字號</button> <div>{{ post.content }}</div> </div> `, })

```html

```

js const vm = new Vue({ el: '#app', data: { posts: [ { title: '標題1', content: '正文內容', id: 0, }, { title: '標題2', content: '正文內容', id: 1, }, { title: '標題3', content: '正文內容', id: 2, }, ], postFontSize: 1 } })

可以看到每一個博文元件中,都有一個按鈕,可以去放大頁面中字型的字號,也就是說,當點選這個按鈕時,我們要告訴父元件改變postFontSize資料去放大所有博文的文字。碰見這樣的情況,該如何做呢?

Vue 例項提供了一個自定義事件來解決這個問題。父元件可以像處理原生DOM元素一樣,通過 v-on指令,監聽子元件例項的任意事件,如:

```html

```

那麼,怎麼樣能夠去監聽到一個 enlarge-text這麼奇怪的事件呢?這就需要在元件內,去主動觸發一個自定義事件了。

如何觸發? 通過呼叫 $emit 方法 並傳入事件名稱來觸發一個事件,如:

js Vue.component('blog-post', { props: { ... }, template: ` <div class="blog-post"> ... <button @click="$emit('enlarge-text')">放大字號</button> ... </div> `, })

這樣,父元件就可以接收該事件,更新資料 pageFontSize 的值了。

使用事件丟擲一個值

在有些情況下,我們可能想讓 <blog-post>元件決定它的文字要放大多少。這是可以使用 $emit 的第二個引數來提供這個值,如:

js Vue.component('blog-post', { props: { ... }, template: ` <div class="blog-post"> ... <button @click="$emit('enlarge-text', 0.2)">放大字號</button> ... </div> `, })

在父元件監聽這個事件時,可以通過 $event 訪問到被丟擲的這個值:

```html

```

或者,將這個事件處理函式寫成一個方法:

```html

```

那麼,這個值將會作為第一個引數,傳入這個方法:

js methods: { onEnlargeText: function (enlargeAmount) { this.postFontSize += enlargeAmount } }

事件名

不同於元件和prop,事件名不存在任何自動化的大小寫轉換。而是觸發的事件名需要完全匹配監聽這個事件所有的名稱。如果觸發一個camelCase名字的事件:

js this.$emit('myEvent')

則監聽這個名字的kabab-case版本是不會有任何效果的。

```html

```

與元件和prop不同的是,事件名不會被當作一個 JS 變數名或者屬性名,所以就沒有理由使用camelCase 或 PascalCase 了。

並且 v-on 事件監聽器在 DOM 模板中會被自動轉換為全小寫,所以 @myEvent 將會變成 @myevent,導致 myEvent 不可能被監聽到。

因此,推薦始終使用 kebab-case 的事件名

將原生事件繫結到元件

在元件上去監聽事件時,我們監聽的是元件的自動觸發的自定義事件,但是在一些情況下,我們可能想要在一個元件的根元素上直接監聽一個原生事件。這是,可以使用 v-on 指令的 .native 修飾符,如:

html <base-input @focus.native="onFocus" @blur.native="onBlur"></base-input>

js Vue.component('base-input', { template: ` <input type="text" /> ` })

這樣處理,在有些時候是很有用的,不過在嘗試監聽一個類似<input>元素時,這並不是一個好主意,例如<base-input>元件可能做了重構,如:

html <label> 姓名: <input type="text"> </label>

可以看到,此時元件的根元素實際上是一個

為了解決這個問題,Vue提供了一個$listeners屬性,它是一個物件,裡面包含了作用在這個元件上的所有監聽器。例如:

js { focus: function (event) { /* ... */ } blur: function (event) { /* ... */ }, }

有了這個 $listeners 屬性,我們可以配合 v-on="$listeners" 將所有的事件監聽器指向這個元件的某個特定的子元素,如:

js Vue.component('base-input', { template: ` <label> 名字: <input v-on="$listeners" /> </label> ` })

在元件上使用 v-model

由於自定義事件的出現,在元件上也可以使用v-model指令。

在 input 元素上使用v-mode指令時,相當於綁定了value特性以及監聽了input事件:

html <input v-model="searchText" />

等價於:

```html <input :value="searchText" @input="searchText = $event.target.value"

```

當把v-model指令用在元件上時:

html <base-input v-model="searchText" />

則等價於:

html <base-input :value="searchText" @input="searchText = $event" />

同 input 元素一樣,在元件上使用v-model指令,也是綁定了value特性,監聽了input事件。

所以,為了讓 v-model 指令正常工作,這個元件內的<input>必須:

  • 將其value特性繫結到一個叫 value 的prop 上
  • 在其input事件被觸發時,將新的值通過自定義的input事件丟擲 如:

js Vue.component('base-input', { props: ['value'], template: ` <input :value="value" @input="$emit('input', $event.target.value)" /> ` })

這樣操作後,v-model就可以在這個元件上工作起來了。

通過上面的學習,我們知道了,一個元件上的 v-model 預設會利用名為 value 的 prop 和名為 input 的事件,但是像單選框、複選框等型別的輸入控制元件可能會將 value 特性用於不同的目的。碰到這樣的情況,我們可以利用 model 選項來避免衝突:

js Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)" > ` })

使用元件:

html <base-checkbox v-model="lovingVue"></base-checkbox>

這裡的 lovingVue 的值將會傳入這個名為 checked 的 prop。同時當 觸發一個 change 事件並附帶一個新的值的時候,這個 lovingVue 的屬性將會被更新。

.sync 修飾符

除了使用 v-model 指令實現元件與外部資料的雙向繫結外,我們還可以用 v-bind 指令的修飾符 .sync 來實現。

那麼,該如何實現呢? 先回憶一下,不利用 v-model 指令來實現元件的雙向資料繫結:

html <base-input :value="searchText" @input="searchText = $event"></base-input>

js Vue.component('base-input', { props: ['value'], template: ` <input :value="value" @input="$emit('input', $event.target.value)" /> ` })

那麼,我們也可以試著,將監聽的事件名進行更改,如:

html <base-input :value="searchText" @update:value="searchText = $event"></base-input>

js Vue.component('base-input', { props: ['value'], template: ` <input :value="value" @input="$emit('update:value', $event.target.value)" /> ` })

這樣也是可以實現雙向資料繫結的,那麼和 .sync 修飾符 有什麼關係呢? 此時,我們對程式碼進行修改:

html <base-input :value.sync="searchText"></base-input>

js Vue.component('base-input', { props: ['value'], template: ` <input :value="value" @input="$emit('update:value', $event.target.value)" /> ` })

所以,.sync 修飾符 本質上也是一個語法糖,在元件上使用:

html <base-input :value.sync="searchText"></base-input>

等價於:

html <base-input :value="searchText" @update:value="searchText = $event" />

當我們用一個物件同時設定多個prop時,也可以將.sync修飾符和 v-bind配合使用:

html <base-input v-bind.sync="obj"></base-input>

注意:

  • 帶有.sync修飾符的v-bind指令,只能提供想要繫結的屬性名,不能和表示式一起使用,如::title.sync="1+1",這樣操作是無效的
  • 將 v-bind.sync 用在 一個字面量物件上,如 v-bind.sync="{ title: 'haha' }",是無法工作的,因為在解析一個像這樣的複雜表示式的時候,有很多邊緣情況需要考慮。

v-model VS .sync

先明確一件事情,在 vue 1.x 時,就已經支援 .sync 語法,但是此時的 .sync 可以完全在子元件中修改父元件的狀態,造成整個狀態的變換很難追溯,所以官方在2.0時移除了這個特性。然後在 vue2.3時,.sync又迴歸了,跟以往不同的是,現在的.sync完完全全就是一個語法糖的作用,跟v-model的實現原理是一樣的,也不容易破環院有的資料模型,所以使用上更安全也更方便。

  • 兩者都是用於實現雙向資料傳遞的,實現方式都是語法糖,最終通過 prop + 事件 來達成目的。
  • vue 1.x 的 .sync 和 v-model 是完全兩個東西,vue 2.3 之後可以理解為一類特性,使用場景略微有區別
  • 當一個元件對外只暴露一個受控的狀態,切都符合統一標準的時候,我們會使用v-model來處理。.sync則更為靈活,凡是需要雙向資料傳遞時,都可以去使用。

最後

如果對您有幫助,希望能給個👍評論/收藏/三連!

博主為人老實,無償解答問題哦❤

  • 歡迎在評論區討論,掘金官方將在掘力星計劃活動結束後,在評論區抽送100份掘金周邊實物,抽獎詳情見活動文章,小夥伴討論起來了!!
「其他文章」