元件化是 Vue 框架的重要思想之一,在前端框架還未出現的時候,通常一個網站頁面就是一個檔案,如果一個頁面有什麼資料需要大家使用,直接宣告一個全域性變數就好了。但是 Vue 框架出現後,將一個頁面元件化了,意味著一個頁面被分割為了很多個檔案,那麼元件之間資料的共用就成了一大問題,當然 Vue 為實現元件間的資料共用提供了很多種方法,今天我們就梳理一下到底有哪些方法?(學習視訊分享:)
因為專案中常用的就那麼幾種,所有經常有很多小夥伴在面試的時候說不全,所以還是建議好好理一理。
由於 Vue 所有的元件呈現元件樹的形態,所以元件間的通訊也有很多種情況,大致有以下幾種:
每種場景下建議的通訊方式也不一樣,需要根據不同的場景選擇最合適的元件間通訊方式。
這種方式通常用於父子元件之間的傳值,父元件通過屬性的方式將值傳遞給子元件,子元件通過props進行接收。子元件通過事件的方式向父元件傳遞資料。
初始化專案:
我們建立一個最簡單的Vue專案,分別建了3個元件:parent、child1、child2,然後在APP.vue中引入parent元件,parent元件中引入child1和child2兩個子元件,初始執行介面如下:
接下來我們利用屬性方式從父元件傳遞值給子元件。
父元件範例程式碼:
// src/views/parent.vue <template> <div class="parent-box"> <p>父級元件</p> <div> <button @click="changeMsg">更改資料</button> </div> <child1 :msg="msg"></child1> <child2 :msg="msg"></child2> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { data() { return { msg: "我是父元件的資料", }; }, components: { child1, child2, }, methods: { // 點選按鈕更改資料 changeMsg() { this.msg = "變成小豬課堂"; }, }, }; </script>
我們將父元件中的msg通過:msg="msg"的方式傳遞給子元件,並且點選按鈕的時候會修改父元件中的msg。
子元件範例程式碼:
// src/views/child1.vue <template> <div class="child-1"> <p>child1元件</p> <div> <p>parent元件資料:{{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: "", }, }, }; </script>
子元件通過props屬性的方式接收父元件傳來的資料。
輸出結果:
當我們點選按鈕的時候,父元件的資料發生變化,子元件接收的資料也跟著發生了變化。
注意::msg="msg"接收的msg是一個變數,可以參考bind的使用原理,不加:則接收的就是一個字串。
子元件可以通過$emit自定義事件的方式向父元件傳遞值,父元件需要監聽該事件來進行接收子元件傳來的值。
父元件範例程式碼:
// src/views/parent.vue <template> <div class="parent-box"> <p>父級元件</p> <div> <button @click="changeMsg">更改資料</button> </div> <div>子元件資料:{{ childData }}</div> <child1 :msg="msg" @childData="childData"></child1> <child2 :msg="msg"></child2> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { data() { return { msg: "我是父元件的資料", childData: "", }; }, components: { child1, child2, }, methods: { changeMsg() { this.msg = "變成小豬課堂"; }, // 監聽子元件事件 childData(data) { this.childData = data; }, }, }; </script>
子元件範例程式碼:
// src/views/child1.vue <template> <div class="child-1"> <p>child1元件</p> <div> <button @click="sendData">傳遞資料給父元件</button> </div> <div> <p>parent元件資料:{{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: "", }, }, methods: { // 點選按鈕,使用$emit向父元件傳遞資料 sendData() { this.$emit("childData", "我是子元件資料"); }, }, }; </script>
輸出結果:
我們在父元件中通過@childData="getChildData"的方式來監聽childData事件,從而獲取子元件傳遞的資料,子元件中通過點選按鈕觸發$emit事件向父元件傳遞資料。當我們點選按鈕「傳遞資料給父元件」時,父元件便可以獲取到資料。
這種方式可以讓子元件非常方便的獲取父元件的值,不僅僅包括資料,還可以是方法。
子元件範例程式碼:
// src/views/child1.vue <template> <div class="child-1"> <p>child1元件</p> <div> <button @click="sendData">傳遞資料給父元件</button> </div> <div> <button @click="getParentData">使用$parent</button> </div> <div> <p>parent元件資料:{{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: "", }, }, methods: { sendData() { this.$emit("childData", "我是子元件資料"); }, // 通過$parent方式獲取父元件值 getParentData() { console.log("父元件", this.$parent); }, }, }; </script>
點選「使用parent獲取父元件的屬性或資料。
輸出結果:
我們可以看到控制檯列印出了父元件的所有屬性,不僅僅包含了data資料,還有裡面定義的一些方法等。
$children
和$refs
獲取子元件值這兩種方式和$parent非常的類似,它們可以直接獲取子元件的相關屬性或方法,不僅限於資料。
父元件範例程式碼:
// src/views/parent.vue <template> <div class="parent-box"> <p>父級元件</p> <div> <button @click="changeMsg">更改資料</button> </div> <div> <button @click="getChildByRef">使用$children和$refs</button> </div> <div>子元件資料:{{ childData }}</div> <child1 ref="child1" :msg="msg" @childData="getChildData"></child1> <child2 :msg="msg"></child2> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { data() { return { msg: "我是父元件的資料", childData: "", }; }, components: { child1, child2, }, methods: { changeMsg() { this.msg = "變成小豬課堂"; }, // 監聽子元件的自定義事件 getChildData(data) { this.childData = data; }, // 使用$chilren和$refs獲取子元件 getChildByRef() { console.log("使用$children", this.$children); console.log("使用$refs", this.$refs.child1); }, }, }; </script>
輸出結果:
上段程式碼中,我們點選按鈕,分別通過refs的方式獲取到了子元件,從而拿到子元件資料。需要注意的是,refs時,需要在子元件上新增ref屬性,有點類似於直接獲取DOM節點的操作。
$attrs
和$listeners
$attrs
是在Vue2.4.0之後新提出的,通常在多層元件傳遞資料的時候使用。很多小夥伴如果遇到多層元件資料傳遞的場景,他可能會直接選用Vuex進行傳遞,但是如果我們需要傳遞的資料沒有涉及到資料的更新和修改時,建議使用$arrts的方式,畢竟Vuex還是比較重。
官網解釋:
包含了父作用域中不作為 prop 被識別 (且獲取) 的 attribute 繫結 (class 和 style 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高階別的元件時非常有用。
官網的解釋還是比較難理解的,我們可以用更加通俗一點的話在解釋一遍。
通俗解釋:
當父元件傳遞了很多資料給子元件時,子元件沒有宣告props來進行接收,那麼子元件中的attrs"的形式向它的子元件(孫子元件)傳遞資料,孫子元件使用$attrs的方式和它的父元件原理類似。
說的再多可能還是沒有程式碼來得簡單易懂,我們新建一個孫子元件child1-child.vue,編寫之後介面如下:
我們在parent父元件中多傳一點資料給child1元件。
parent元件範例程式碼:
// src/views/parent.vue <template> <div class="parent-box"> <p>父級元件</p> <child1 ref="child1" :msg="msg" :msg1="msg1" :msg2="msg2" :msg3="msg3" :msg4="msg4" @childData="getChildData" ></child1> </div> </template> <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { data() { return { msg: "我是父元件的資料", msg1: "parent資料1", msg2: "parent資料2", msg3: "parent資料3", msg4: "parent資料4", childData: "", }; }, components: { child1, child2, } }; </script>
這裡我們刪除了一些本節用不到的程式碼,大家需要注意一下。
child1元件範例程式碼:
// src/views/child1.vue <template> <div class="child-1"> <p>child1元件</p> <!-- 子元件child1-child --> <child1-child v-bind="$attrs"></child1-child> </div> </template> <script> import Child1Child from "./child1-child"; export default { components: { Child1Child, }, props: { msg: { type: String, default: "", }, }, mounted() { console.log("child1元件獲取$attrs", this.$attrs); } }; </script>
輸出結果:
上段程式碼中我們的parent父元件傳遞了5個資料給子元件:msg、msg1、msg2、msg3、msg4。但是在子元件中的props屬性裡面,我們只接收了msg。然後我們在子元件mounted中列印了$attrs,發現恰好少了props接收過的msg資料。
當我們在child1元件中使用attrs"的形式在傳遞給它的子元件child1-child,上段程式碼中我們已經加上了v-bind。
child1-child元件範例程式碼:
// src/views/child1-child.vue <template> <div class="child1-child"> <p>我是孫子元件child1-child</p> </div> </template> <script> export default { props: { msg1: { type: String, default: "", }, }, mounted() { console.log("child1-child元件$attrs", this.$attrs); }, }; </script>
輸出結果:
我們發現child1-child元件中列印的$attrs中少了msg1,因為我們已經在props中接收了msg1。
attrs屬性和型別,只是它們傳遞的東西不一樣。
官網的解釋:
包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用。
通俗的解釋:
當父元件在子元件上定義了一些自定義的非原生事件時,在子元件內部可以通過$listeners屬性獲取到這些自定義事件。
它和attrs用來傳遞屬性,$listeners用來傳遞非原生事件,我們在child1元件中列印一下看看。
child1元件範例程式碼:
// src/views/child1.vue mounted() { console.log("child1元件獲取$attrs", this.$attrs); console.log("child1元件獲取$listeners", this.$listeners); },
輸出結果:
可以發現輸出了childData方法,這是我們在它的父元件自定義的監聽事件。除次之外,$listeners可以通過v-on的形式再次傳遞給下層元件。
child1元件範例程式碼:
// src/views/child1.vue <template> <div class="child-1"> <p>child1元件</p> <div> <button @click="sendData">傳遞資料給父元件</button> </div> <div> <button @click="getParentData">使用$parent</button> </div> <div> <p>parent元件資料:{{ msg }}</p> </div> <!-- 子元件child1-child --> <child1-child v-bind="$attrs" v-on="$listeners"></child1-child> </div> </template>
child1-child元件範例程式碼:
// src/views/child1-child.vue mounted() { console.log("child1-child元件$attrs", this.$attrs); console.log("child1-child元件$listerners", this.$listeners); },
輸出結果:
可以看到在child1-child孫子元件中也獲得了parent父元件中的childData自定義事件。使用emit的方式逐級向上觸發事件,只需要使用$listerners就可以得到父元件中的自定義事件,相當於偷懶了。
可能細心的小夥伴會發現,我們在使用$attrs時,child1子元件渲染的DOM節點上將我們傳遞的屬性一起渲染了出來,如下圖所示:
這並不是我們想要的,為了解決這個問題,我們可以在子元件中設定inheritAttrs屬性。
官網解釋:
預設情況下父作用域的不被認作 props 的 attribute 繫結 (attribute bindings) 將會「回退」且作為普通的 HTML attribute 應用在子元件的根元素上。當撰寫包裹一個目標元素或另一個元件的元件時,這可能不會總是符合預期行為。通過設定 inheritAttrs 到 false,這些預設行為將會被去掉。而通過 (同樣是 2.4 新增的) 範例 property $attrs 可以讓這些 attribute 生效,且可以通過 v-bind 顯性的繫結到非根元素上。
官網說了非常多,但是不太通俗,我們可以簡單的理解。
通俗解釋:
父元件傳遞了很多資料給子元件,子元件的props沒有完全接收,那麼父元件傳遞的這些資料就會渲染到HTML上,我們可以給子元件設定inheritAttrs 為false,避免這樣渲染。
child1元件範例程式碼:
// src/views/child1.vue props: { msg: { type: String, default: "", }, }, inheritAttrs: false,
輸出結果:
此時我們節點上就沒有那些無關的節點屬性了。
在我們做專案的時候,會發現不相關的元件之間的資料傳遞是較為麻煩的,比如兄弟元件、跨級元件,在不使用Vuex情況下,我們可以使用自定義事件(也可以稱作事件中心)的方式來實現資料傳遞。
事件中心的思想也比較簡單:中間中心主要就兩個作用:觸發事件和監聽事件。假如兩個元件之間需要傳遞資料,元件A可以觸發事件中心的事件,元件B監聽事件中心的事件,從而讓兩個元件之間產生關聯,實現資料傳遞。
實現步驟:
為了演示簡單,我們在全域性註冊一個事件中心,修改main.js。
main.js程式碼如下:
// src/main.js Vue.config.productionTip = false Vue.prototype.$EventBus = new Vue() new Vue({ router, store, render: h => h(App) }).$mount('#app')
child1元件範例程式碼:
<template> <div class="child-1"> <p>child1元件</p> <div> <button @click="toChild2">向child2元件傳送資料</button> </div> </div> </template> <script> import Child1Child from "./child1-child"; export default { methods: { // 通過事件匯流排向child2元件傳送資料 toChild2() { this.$EventBus.$emit("sendMsg", "我是child1元件發來的資料"); }, }, }; </script>
child1元件中呼叫emit向事件中心新增sendMsg事件,這個用法有點類似與props和$emit的關係。
child2元件2範例程式碼:
// src/views/child1.vue <template> <div class="child-2"> <p>child2元件</p> <div> <p>parent元件資料:{{ msg }}</p> </div> </div> </template> <script> export default { props: { msg: { type: String, default: "", }, }, mounted() { this.$EventBus.$on("sendMsg", (msg) => { console.log("接收到child1傳送來的資料", msg); }); }, }; </script>
當我們點選child1元件中的按鈕時,就會觸發sendMsg事件,在child2元件中我們監聽了該事件,所以會接收到child1元件發來的資料。
輸出結果:
事件中心實現資料傳遞的這種方式,其實就是一個釋出者和訂閱者的模式,這種方式可以實現任何元件之間的通訊。
這兩個是在Vue2.2.0新增的API,provide和inject需要在一起使用。它們也可以實現元件之間的資料通訊,但是需要確保元件之間是父子關係。
官網的解釋:
這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在其上下游關係成立的時間裡始終生效。
官網的解釋就已經說得很明確了,所以這裡我們就不需要通俗的解釋了,簡單一句話:父元件可以向子元件(無論層級)注入依賴,每個子元件都可以獲得這個依賴,無論層級。
parent範例程式碼:
// src/views/parent.vue <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { provide() { return { parentData: this.msg }; }, data() { return { msg: "我是父元件的資料", msg1: "parent資料1", msg2: "parent資料2", msg3: "parent資料3", msg4: "parent資料4", childData: "", }; }, components: { child1, child2, }, }; </script>
child1-child元件範例程式碼:
// src/views/child1-child.vue <template> <div class="child1-child"> <p>我是孫子元件child1-child</p> <p>parent元件資料:{{parentData}}</p> </div> </template> <script> export default { inject: ["parentData"], props: { msg1: { type: String, default: "", }, }, mounted() { console.log("child1-child元件$attrs", this.$attrs); console.log("child1-child元件$listerners", this.$listeners); console.log("child1-child元件獲取parent元件資料", this.parentData) }, }; </script>
輸出結果:
通過provide和inject結合的方式,我們在child1-child元件中獲取到了parent元件中的資料。如果你下來嘗試過的話,可能會發現一個問題,此時資料不是響應式,也就是parent元件更改了資料,child1-child元件中的資料不會更新。
想要變為響應式的,我們需要修改一下provide傳遞的方式。
parent程式碼如下:
// src/views/parent.vue <script> import child1 from "./child1.vue"; import child2 from "./child2.vue"; export default { provide() { return { parentData: this.getMsg }; }, data() { return { msg: "我是父元件的資料", msg1: "parent資料1", msg2: "parent資料2", msg3: "parent資料3", msg4: "parent資料4", childData: "", }; }, components: { child1, child2, }, methods: { // 返回data資料 getMsg() { return this.msg; }, }, }; </script>
這個時候我們會發現資料變為響應式的了。
porvide和inject的原理可以參考下圖:
這兩種方式應該是小夥伴們在實際專案中使用最多的了,所以這裡就不但展開細說,只是提一下這兩者的區別即可。
Vuex:
localstorage:
v-model是vue中的一個內建指令,它通常用在表單元素上以此來實現資料的雙向繫結,它的本質是v-on和v-bind的語法糖。在這裡我們也可以藉助它來實現某些場景下的資料傳遞。注意,這兒的場景必須是父子元件。
parent元件範例程式碼:
<template> <div class="parent-box"> <p>父級元件</p> <div>modelData: {{modelData}}</div> <child2 :msg="msg" v-model="modelData"></child2> <!-- 實際等同於 --> <!-- <child2 v-bind:value="modelData" v-on:input="modelData=$event"></child2> --> </div> </template> <script> import child2 from "./child2.vue"; export default { provide() { return { parentData: this.getMsg }; }, data() { return { modelData: "parent元件的model資料" }; }, components: { child1, }, }; </script>
child2元件範例程式碼:
<template> <div class="child-2"> <p>child2元件</p> <div> <button @click="confirm">修改v-model資料</button> </div> </div> </template> <script> export default { props: { value: { type: String, default: "", }, }, mounted() { console.log("child2元件接收附件見v-model傳遞的資料", this.value); }, methods: { // 通過$emit觸發父元件的input事件,並將第二個引數作為值傳遞給父元件 confirm() { this.$emit("input", "修改parent傳遞的v-model資料"); }, }, }; </script>
我們在父元件中使用v-model向child2子元件傳遞資料,子元件的props中使用預設的value屬性接收,在子元件中利用$emit觸發父元件中預設input事件,此時傳遞的資料便會在子元件和父元件中發生變化,這就是資料雙向繫結。
如果想要更加詳細的學習v-model的使用,可以參考官網。
Vue中元件通訊的方式有很多種,每一種應用的場景可能都有一些不一樣,我們需要在合適的場景下選擇合適的通訊方式。
(學習視訊分享:、)
以上就是總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!的詳細內容,更多請關注TW511.COM其它相關文章!