總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!

2022-04-18 22:00:07
面試官:你有多少種方式實現元件間通訊?下面本篇文章給大家總結分享幾種實現Vue元件間通訊的方式,再也不怕面試了,希望對大家有所幫助!

元件化是 Vue 框架的重要思想之一,在前端框架還未出現的時候,通常一個網站頁面就是一個檔案,如果一個頁面有什麼資料需要大家使用,直接宣告一個全域性變數就好了。但是 Vue 框架出現後,將一個頁面元件化了,意味著一個頁面被分割為了很多個檔案,那麼元件之間資料的共用就成了一大問題,當然 Vue 為實現元件間的資料共用提供了很多種方法,今天我們就梳理一下到底有哪些方法?(學習視訊分享:)

因為專案中常用的就那麼幾種,所有經常有很多小夥伴在面試的時候說不全,所以還是建議好好理一理。

1.那些場景需要通訊?

由於 Vue 所有的元件呈現元件樹的形態,所以元件間的通訊也有很多種情況,大致有以下幾種:

  • 父子元件間通訊
  • 兄弟元件間通訊
  • 隔代元件間通訊
  • 無相關元件間通訊

每種場景下建議的通訊方式也不一樣,需要根據不同的場景選擇最合適的元件間通訊方式。

2.props 和$emit

這種方式通常用於父子元件之間的傳值,父元件通過屬性的方式將值傳遞給子元件,子元件通過props進行接收。子元件通過事件的方式向父元件傳遞資料。

初始化專案:

我們建立一個最簡單的Vue專案,分別建了3個元件:parent、child1、child2,然後在APP.vue中引入parent元件,parent元件中引入child1和child2兩個子元件,初始執行介面如下:

1.png

2.png

2.1 父元件傳值給子元件

接下來我們利用屬性方式從父元件傳遞值給子元件。

父元件範例程式碼:

// 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屬性的方式接收父元件傳來的資料。

輸出結果:

3.png

當我們點選按鈕的時候,父元件的資料發生變化,子元件接收的資料也跟著發生了變化。

注意::msg="msg"接收的msg是一個變數,可以參考bind的使用原理,不加:則接收的就是一個字串。

2.2 子元件傳值給父元件

子元件可以通過$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>

輸出結果:

4.png

我們在父元件中通過@childData="getChildData"的方式來監聽childData事件,從而獲取子元件傳遞的資料,子元件中通過點選按鈕觸發$emit事件向父元件傳遞資料。當我們點選按鈕「傳遞資料給父元件」時,父元件便可以獲取到資料。

3.$parent獲取父元件值

這種方式可以讓子元件非常方便的獲取父元件的值,不僅僅包括資料,還可以是方法。

子元件範例程式碼:

// 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」按鈕時,通過parent」按鈕時,通過parent獲取父元件的屬性或資料。

輸出結果:

5.png

我們可以看到控制檯列印出了父元件的所有屬性,不僅僅包含了data資料,還有裡面定義的一些方法等。

4.$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>

輸出結果:

6.png

上段程式碼中,我們點選按鈕,分別通過childrenchildren和refs的方式獲取到了子元件,從而拿到子元件資料。需要注意的是,children會返回當前元件所包含的所有子元件陣列,使用children會返回當前元件所包含的所有子元件陣列,使用refs時,需要在子元件上新增ref屬性,有點類似於直接獲取DOM節點的操作。

5.使用$attrs$listeners

$attrs是在Vue2.4.0之後新提出的,通常在多層元件傳遞資料的時候使用。很多小夥伴如果遇到多層元件資料傳遞的場景,他可能會直接選用Vuex進行傳遞,但是如果我們需要傳遞的資料沒有涉及到資料的更新和修改時,建議使用$arrts的方式,畢竟Vuex還是比較重。

官網解釋:

包含了父作用域中不作為 prop 被識別 (且獲取) 的 attribute 繫結 (class 和 style 除外)。當一個元件沒有宣告任何 prop 時,這裡會包含所有父作用域的繫結 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高階別的元件時非常有用。

官網的解釋還是比較難理解的,我們可以用更加通俗一點的話在解釋一遍。

通俗解釋:

當父元件傳遞了很多資料給子元件時,子元件沒有宣告props來進行接收,那麼子元件中的attrs屬性就包含了所有父元件傳來的資料(除開已經props宣告了的),子元件還可以使用vbind="attrs屬性就包含了所有父元件傳來的資料(除開已經props宣告了的),子元件還可以使用v-bind="attrs"的形式向它的子元件(孫子元件)傳遞資料,孫子元件使用$attrs的方式和它的父元件原理類似。

說的再多可能還是沒有程式碼來得簡單易懂,我們新建一個孫子元件child1-child.vue,編寫之後介面如下:

7.png

5.1 $attrs的使用

我們在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>

輸出結果:

8.png

上段程式碼中我們的parent父元件傳遞了5個資料給子元件:msg、msg1、msg2、msg3、msg4。但是在子元件中的props屬性裡面,我們只接收了msg。然後我們在子元件mounted中列印了$attrs,發現恰好少了props接收過的msg資料。

當我們在child1元件中使用attrs接收了元件後,可以使用vbind="attrs接收了元件後,可以使用v-bind="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>

輸出結果:

9.png

我們發現child1-child元件中列印的$attrs中少了msg1,因為我們已經在props中接收了msg1。

5.2 $listeners 的使用

listeners屬性和listeners屬性和attrs屬性和型別,只是它們傳遞的東西不一樣。

官網的解釋:

包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件——在建立更高層次的元件時非常有用。

通俗的解釋:

當父元件在子元件上定義了一些自定義的非原生事件時,在子元件內部可以通過$listeners屬性獲取到這些自定義事件。

它和attrs的區別很明顯,attrs的區別很明顯,attrs用來傳遞屬性,$listeners用來傳遞非原生事件,我們在child1元件中列印一下看看。

child1元件範例程式碼:

// src/views/child1.vue
mounted() {
  console.log("child1元件獲取$attrs", this.$attrs);
  console.log("child1元件獲取$listeners", this.$listeners);
},

輸出結果:

10.png

可以發現輸出了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);
},

輸出結果:

11.png

可以看到在child1-child孫子元件中也獲得了parent父元件中的childData自定義事件。使用listeners的好處在於:如果存在多層級元件,無需使用listeners的好處在於:如果存在多層級元件,無需使用emit的方式逐級向上觸發事件,只需要使用$listerners就可以得到父元件中的自定義事件,相當於偷懶了。

5.3 inheritAttrs

可能細心的小夥伴會發現,我們在使用$attrs時,child1子元件渲染的DOM節點上將我們傳遞的屬性一起渲染了出來,如下圖所示:

12.png

這並不是我們想要的,為了解決這個問題,我們可以在子元件中設定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,

輸出結果:

13.png

此時我們節點上就沒有那些無關的節點屬性了。

5.4 總結

  • $attrs:用來傳遞屬性,出了class、style之類的,它是一個物件。
  • $listeners:用來傳遞事件,出了原生事件,它也是一個物件。
  • attrsattrs和listeners這兩個屬性可以解決多層元件之間資料和事件傳遞的問題。
  • inheritAttrs解決未使用props接收的資料的屬性渲染。

6.自定義事件:事件匯流排

在我們做專案的時候,會發現不相關的元件之間的資料傳遞是較為麻煩的,比如兄弟元件、跨級元件,在不使用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元件中呼叫EventBus.EventBus.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元件發來的資料。

輸出結果:

14.png

事件中心實現資料傳遞的這種方式,其實就是一個釋出者和訂閱者的模式,這種方式可以實現任何元件之間的通訊。

7.provide和inject

這兩個是在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>

輸出結果:

15.png

通過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的原理可以參考下圖:

16.png

8.Vuex和localStorage

這兩種方式應該是小夥伴們在實際專案中使用最多的了,所以這裡就不但展開細說,只是提一下這兩者的區別即可。

Vuex:

  • Vuex是狀態管理器,它儲存的資料不是持久化儲存,一旦重新整理頁面或者關閉專案資料便不見了。
  • Vuex儲存的資料是響應式的。

localstorage:

  • loacalStorage是HTML5中的一種資料儲存方式,持久化儲存,儲存的資料不是響應式的。

9.v-model

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中元件通訊的方式有很多種,每一種應用的場景可能都有一些不一樣,我們需要在合適的場景下選擇合適的通訊方式。

  • 父子元件間通訊:props和emitemit、parent、refsrefs和children、v-model
  • 兄弟元件間通訊:事件匯流排、Vuex、localStorage
  • 隔代元件間通訊:provide和inject
  • 無相關元件間通訊:事件匯流排、Vuex、localStorage

(學習視訊分享:、)

以上就是總結分享Vue中實現元件間通訊的多種方式,再也不怕面試了!的詳細內容,更多請關注TW511.COM其它相關文章!