vue 元件通訊總結 (非vuex和Event Bus)
父元件通過 props
傳遞資料給子元件,子元件通過 emit
傳送事件傳遞資料給父元件。這是最常用的父子元件通訊方式,符合單向資料流,即子元件不能直接修改 props, 而是必須通過傳送事件的方式告知父元件修改資料。由於是常用的方式,在這也不多囉嗦了。
v-model 方式
v-model
實現的通訊其本質上還是上面的 props
和 emit
方式,使用 v-model
更像是一種語法糖。文件介紹
先舉個栗子:
// 這是父元件 <template> <div> <child v-model="msg"></child> <p>{{msg}}</p> </div> </template> <script> import child from "../components/Child"; export default { data() { return { msg: "hello" }; }, components: { child } }; </script> 複製程式碼
// 這是子元件 <template> <div> <input :value="value" @input="$emit('input',$event.target.value)"> </div> </template> <script> export default { props: ["value"] }; </script> 複製程式碼
父元件使用子元件時,使用 v-model
繫結父元件 msg
資料,這會在子元件裡解析成名為 value
的 prop 和名為 input
的事件,所以子元件裡的 props
選項裡必須寫成 value
,在 $emit
事件裡也需寫成 input
事件。此時當你在子元件輸入時,就會改變父元件的 msg
值。
使用 model 選項自定義 props 和 event
上面說了,props選項裡必須寫 value
,事件也必須是 input
。這是預設情況下的解析,其實我們也可以自定義 props 和 event,使用 model
選項,文件介紹。文件中以複選框為例,修改 props 和 event:
model: { prop: 'checked', event: 'change' } 複製程式碼
$children
&& $parent
方式
這兩個是vue提供的api,見名知意,在父元件裡使用 $children
訪問子元件,在子元件裡使用 $parent
訪問父元件。
舉個簡單栗子:
// 這是子元件 <template> <div> {{$parent.msg}}// 子元件顯示父元件資料 </div> </template> <script> export default { data() { return { child_msg: "我是子元件資料" }; }, mounted() { this.$parent.test(); // 子元件執行父元件方法 } }; </script> 複製程式碼
// 這是父元件 <template> <div> <child/> </div> </template> <script> import child from "../components/Child"; export default { data() { return { msg: "我是父元件的資料" }; }, components: { child }, methods: { test() { console.log("我是父元件的方法,被執行"); } }, mounted() { console.log(this.$children[0].child_msg); // 執行子元件方法 } }; </script> 複製程式碼
【注意】 $children
是陣列,所以當只有一個子元件時,使用 [0]
獲取。當有多個子元件時,它並不保證順序,也不是響應式的。
$listeners
方式
初看此api的定義,我也是似懂非懂:
包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用
文件這裡也描述了它的使用方法:文件介紹
在檢視一些部落格時,要麼拿官方例子,要麼一大堆介紹,其實我看的也是一臉懵逼。後來自己慢慢試著用了下,也大概明白它是幹嘛的。我的理解: 在多層巢狀元件的業務中,使用 $listeners
可以使用更少的程式碼來完成事件通訊。
還是以程式碼來說明,如下圖,我們來實現元件B 到 父元件 的通訊,

一般巢狀層級太多時,我們可能就會考慮vuex,但只傳遞資料,而不做中間處理,有點大材小用,所以如上圖這樣的,我們可能還是使用 emit
方式來通訊,無非多傳一層,多寫點程式碼。那麼現在,有了 $listeners
,我們可以更方便的來實現,我儘量用最少的程式碼來實現下:
就從最下面的B元件開始,它有一個按鈕,點選時觸發例項上的事件 getFromB
// 元件B <template> <div> <button @click="handleClick">B元件按鈕</button> </div> </template> <script> export default { methods: { handleClick() { this.$emit("getFromB"); } } }; </script> 複製程式碼
A元件 包裹 B元件,相當於是父元件與B元件的中轉站,在不用 $listeners
時,我們可能會在這裡再觸發一個事件,現在不需要這樣了,我們這樣:
// 元件A <template> <div> <child-b v-on="$listeners" /> </div> </template> <script> import childB from "../components/ChildB"; export default { components: { childB }, mounted() { console.log(this.$listeners); } }; </script> 複製程式碼
只需要加一句 v-on="$listeners"
即可。好奇的我們也可以 mounted 時列印一下 $listeners
。
父元件,顯而易見,我們直接繫結 getFromB
事件即可:
// 父元件 <template> <div> <child-a v-on:getFromB="fromB"/> </div> </template> <script> import childA from "../components/ChildA"; export default { components: { childA }, methods: { fromB() { console.log("B元件觸發"); } } }; </script> 複製程式碼
這就是 $listeners
的簡單用法,說到這裡,你應該意識到,當元件巢狀很多層時,不借助 vuex,我們也可以較方便地實現通訊了。
說到這裡,我還要提一個api,就是 $attrs
。它與 $listeners
的關係就好比 props 與 emit 的關係,用來向底層元件傳遞屬性。先貼上它的定義:
包含了父作用域中不作為 prop 被識別 (且獲取) 的特性繫結 (class 和 style 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高級別的元件時非常有用。
我們回想下,如果使用 props 向孫元件傳遞資料時,在中間元件裡,我們是要一層層使用 props 選項來接收,然後再傳遞的。那麼 $attrs
的作用就是在沒到目標子元件時,不使用props接收資料,直到到達需要資料的元件時,再使用props接收。
在我看別的部落格時,都是這兩個api一起說的,程式碼比較多,為了清晰,我把上面程式碼多餘的程式碼刪掉,只演示 $attrs
的使用:
父元件傳遞一個屬性 toB
,意為是給B元件用的:
// 父元件 <template> <div> <child-a toB="hello"/> </div> </template> <script> import childA from "../components/ChildA"; export default { components: { childA } }; </script> 複製程式碼
A元件使用 v-bind="$attrs"
即可,不需要 props 接收,實際上也不可以接收,看定義
// 元件 A <template> <div> <child-b v-bind="$attrs" /> </div> </template> <script> import childB from "../components/ChildB"; export default { components: { childB } }; </script> 複製程式碼
B元件是我們的最後子元件,它用到 toB
屬性,所以使用 props 選項接收了
<template> <div> <p>父元件傳來資料:{{toB}}</p> </div> </template> <script> export default { props: ["toB"] }; </script> 複製程式碼
從這個簡單的例子,我們可以知道,當元件巢狀層級很多時,屬性傳遞變得不要太方便。最後還要提一個 inheritAttrs
選項,它一般配合 $attrs
使用,這裡我就不再多說了。文件介紹
.sync
方式
此方法其實用的也不少,它在 Vue 1.x 裡的作用是對一個 prop 進行“雙向繫結“。但在 Vue 2 之後是隻允許單向資料流的,所以現在即使它看起來像是真正的“雙向繫結”,本質上也只是作為一個編譯時的語法糖存在而已。
舉個計數器的例子:
// 父元件 <template> <div> {{num}} <child-a :count.sync="num" /> </div> </template> <script> import childA from "../components/ChildA"; export default { data() { return { num: 0 }; }, components: { childA } }; </script> 複製程式碼
// 子元件 <template> <div> <div @click="handleAdd">ADD</div> </div> </template> <script> export default { data() { return { counter: this.count }; }, props: ["count"], methods: { handleAdd() { this.$emit("update:count", ++this.counter); } } }; </script> 複製程式碼
嗯,看起來似乎更有逼格。