方式一覽
props
&&emit
v-model
$children
&&$parent
$listeners
&&$attrs
.sync
prop && emit 方式
父元件通過 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>
複製程式碼
嗯,看起來似乎更有逼格。
結語
這麼看下來,除了$children
和 $parent
是直接獲取的,其他都跟 props 和 emit 息息相關。具體怎麼用,自己看著辦唄。
最後,新年快樂!