天天看點

Vue元件間通信6種方式

摘要: 總有一款合适的通信方式。

Fundebug

經授權轉載,版權歸原作者所有。

前言

元件是 vue.js 最強大的功能之一,而元件執行個體的作用域是互相獨立的,這就意味着不同元件之間的資料無法互相引用。一般來說,元件可以有以下幾種關系:

Vue元件間通信6種方式

如上圖所示,A 和 B、B 和 C、B 和 D 都是父子關系,C 和 D 是兄弟關系,A 和 C 是隔代關系(可能隔多代)。

針對不同的使用場景,如何選擇行之有效的通信方式?這是我們所要探讨的主題。本文總結了 vue 元件間通信的幾種方式,如 props、

$emit

/

$on

、vuex、

$parent

$children

$attrs

$listeners

和 provide/inject,以通俗易懂的執行個體講述這其中的差别及使用場景,希望對小夥伴有些許幫助。

本文的代碼請猛戳

github 部落格

,紙上得來終覺淺,大家動手多敲敲代碼!

方法一、

props

$emit

父元件 A 通過 props 的方式向子元件 B 傳遞,B to A 通過在 B 元件中 $emit, A 元件中 v-on 的方式實作。

1. 父元件向子元件傳值

接下來我們通過一個例子,說明父元件如何向子元件傳遞值:在子元件 Users.vue 中如何擷取父元件 App.vue 中的資料

users:["Henry","Bucky","Emily"]

//App.vue父元件
<template>
  <div id="app">
    <users v-bind:users="users"></users>//前者自定義名稱便于子元件調用,後者要傳遞資料名
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "users":Users
  }
}           
//users子元件
<template>
  <div class="hello">
    <ul>
      <li v-for="user in users">{{user}}</li>//周遊傳遞過來的值,然後呈現到頁面
    </ul>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    users:{           //這個就是父元件中子标簽自定義名字
      type:Array,
      required:true
    }
  }
}
</script>           

總結:父元件通過 props 向下傳遞資料給子元件。注:元件中的資料共有三種形式:data、props、computed

2. 子元件向父元件傳值(通過事件形式)

接下來我們通過一個例子,說明子元件如何向父元件傳遞值:當我們點選“Vue.js Demo”後,子元件向父元件傳遞值,文字由原來的“傳遞的是一個值”變成“子向父元件傳值”,實作子元件向父元件值的傳遞。

Vue元件間通信6種方式
// 子元件
<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>//綁定一個點選事件
  </header>
</template>
<script>
export default {
  name: 'app-header',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","子向父元件傳值");//自定義事件  傳遞值“子向父元件傳值”
    }
  }
}
</script>           
// 父元件
<template>
  <div id="app">
    <app-header v-on:titleChanged="updateTitle" ></app-header>//與子元件titleChanged自定義事件保持一緻
   // updateTitle($event)接受傳遞過來的文字
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"傳遞的是一個值"
    }
  },
  methods:{
    updateTitle(e){   //聲明這個函數
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
</script>           

總結:子元件通過 events 給父元件發送消息,實際上就是子元件把自己的資料發送到父元件。

方法二、

$emit

$on

這種方法通過一個空的 Vue 執行個體作為中央事件總線(事件中心),用它來觸發事件和監聽事件,巧妙而輕量地實作了任何元件間的通信,包括父子、兄弟、跨級。當我們的項目比較大時,可以選擇更好的狀态管了解決方案 vuex。

1.具體實作方式:

var Event=new Vue();
    Event.$emit(事件名,資料);
    Event.$on(事件名,data => {});           

2.舉個例子

假設兄弟元件有三個,分别是 A、B、C 元件,C 元件如何擷取 A 或者 B 元件的資料

<div id="itany">
    <my-a></my-a>
    <my-b></my-b>
    <my-c></my-c>
</div>
<template id="a">
  <div>
    <h3>A元件:{{name}}</h3>
    <button @click="send">将資料發送給C元件</button>
  </div>
</template>
<template id="b">
  <div>
    <h3>B元件:{{age}}</h3>
    <button @click="send">将數組發送給C元件</button>
  </div>
</template>
<template id="c">
  <div>
    <h3>C元件:{{name}},{{age}}</h3>
  </div>
</template>
<script>
var Event = new Vue();//定義一個空的Vue執行個體
var A = {
    template: '#a',
    data() {
      return {
        name: 'tom'
      }
    },
    methods: {
      send() {
        Event.$emit('data-a', this.name);
      }
    }
}
var B = {
    template: '#b',
    data() {
      return {
        age: 20
      }
    },
    methods: {
      send() {
        Event.$emit('data-b', this.age);
      }
    }
}
var C = {
    template: '#c',
    data() {
      return {
        name: '',
        age: ""
      }
    },
    mounted() {//在模闆編譯完成後執行
     Event.$on('data-a',name => {
         this.name = name;//箭頭函數内部不會産生新的this,這邊如果不用=>,this指代Event
     })
     Event.$on('data-b',age => {
         this.age = age;
     })
    }
}
var vm = new Vue({
    el: '#itany',
    components: {
      'my-a': A,
      'my-b': B,
      'my-c': C
    }
});
</script>           
Vue元件間通信6種方式

$on

監聽了自定義事件 data-a 和 data-b,因為有時不确定何時會觸發事件,一般會在 mounted 或 created 鈎子中來監聽。

方法三、vuex

Vue元件間通信6種方式

1. 簡要介紹 Vuex 原理

Vuex 實作了一個單向資料流,在全局擁有一個 State 存放資料,當元件要更改 State 中的資料時,必須通過 Mutation 進行,Mutation 同時提供了訂閱者模式供外部插件調用擷取 State 資料的更新。而當所有異步操作(常見于調用後端接口異步擷取更新資料)或批量的同步操作需要走 Action,但 Action 也是無法直接修改 State 的,還是需要通過 Mutation 來修改 State 的資料。最後,根據 State 的變化,渲染到視圖上。

2. 簡要介紹各子產品在流程中的功能:

  • Vue Components:Vue 元件。HTML 頁面上,負責接收使用者操作等互動行為,執行 dispatch 方法觸發對應 action 進行回應。
  • dispatch:操作行為觸發方法,是唯一能執行 action 的方法。
  • actions:操作行為處理子產品,由元件中的

    $store.dispatch('action 名稱', data1)

    來觸發。然後由 commit()來觸發 mutation 的調用 , 間接更新 state。負責處理 Vue Components 接收到的所有互動行為。包含同步/異步操作,支援多個同名方法,按照注冊的順序依次觸發。向背景 API 請求的操作就在這個子產品中進行,包括觸發其他 action 以及送出 mutation 的操作。該子產品提供了 Promise 的封裝,以支援 action 的鍊式觸發。
  • commit:狀态改變送出操作方法。對 mutation 進行送出,是唯一能執行 mutation 的方法。
  • mutations:狀态改變操作方法,由 actions 中的

    commit('mutation 名稱')

    來觸發。是 Vuex 修改 state 的唯一推薦方法。該方法隻能進行同步操作,且方法名隻能全局唯一。操作之中會有一些 hook 暴露出來,以進行 state 的監控等。
  • state:頁面狀态管理容器對象。集中存儲 Vue components 中 data 對象的零散資料,全局唯一,以進行統一的狀态管理。頁面顯示所需的資料從該對象中進行讀取,利用 Vue 的細粒度資料響應機制來進行高效的狀态更新。
  • getters:state 對象讀取方法。圖中沒有單獨列出該子產品,應該被包含在了 render 中,Vue Components 通過該方法讀取全局 state 對象。

3. Vuex 與 localStorage

vuex 是 vue 的狀态管理器,存儲的資料是響應式的。但是并不會儲存起來,重新整理之後就回到了初始狀态,具體做法應該在 vuex 裡資料改變的時候把資料拷貝一份儲存到 localStorage 裡面,重新整理之後,如果 localStorage 裡有儲存的資料,取出來再替換 store 裡的 state。

let defaultCity = "上海"
try {   // 使用者關閉了本地存儲功能,此時在外層加個try...catch
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 資料改變的時候把資料拷貝一份儲存到localStorage裡面
      } catch (e) {}
    }
  }
})           

這裡需要注意的是:由于 vuex 裡,我們儲存的狀态,都是數組,而 localStorage 隻支援字元串,是以需要用 JSON 轉換:

JSON.stringify(state.subscribeList);   // array -> string
JSON.parse(window.localStorage.getItem("subscribeList"));    // string -> array           

方法四、

$attrs

$listeners

1. 簡介

多級元件嵌套需要傳遞資料時,通常使用的方法是通過 vuex。但如果僅僅是傳遞資料,而不做中間處理,使用 vuex 處理,未免有點大材小用。為此 Vue2.4 版本提供了另一種方法----

$attrs

$listeners

  • $attrs

    :包含了父作用域中不被 prop 所識别 (且擷取) 的特性綁定 (class 和 style 除外)。當一個元件沒有聲明任何 prop 時,這裡會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="&dollar;attrs" 傳入内部元件。通常配合 interitAttrs 選項一起使用。
  • $listeners

    :包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="&dollar;listeners" 傳入内部元件

接下來我們看個跨級通信的例子:

// index.vue
<template>
  <div>
    <h2>浪裡行舟</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端工匠"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>           
// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以關閉自動挂載到元件根元素上的沒有在props聲明的屬性
  props: {
    foo: String // foo作為props屬性綁定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>           
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>           
// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>           
Vue元件間通信6種方式

如上圖所示

$attrs

表示沒有繼承資料的對象,格式為{屬性名:屬性值}。Vue2.4 提供了

$attrs

,

$listeners

來傳遞資料與事件,跨級元件之間的通訊變得更簡單。

簡單來說:

$attrs

$listeners

是兩個對象,

$attrs

裡存放的是父元件中綁定的非 Props 屬性,

$listeners

裡存放的是父元件中綁定的非原生事件。

方法五、provide/inject

Vue2.2.0 新增 API,這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,并在起上下遊關系成立的時間裡始終生效。一言而蔽之:祖先元件中通過 provider 來提供變量,然後在子孫元件中通過 inject 來注入變量。

provide / inject API 主要解決了跨級元件間的通信問題,不過它的使用場景,主要是子元件擷取上級元件的狀态,跨級元件間建立了一種主動提供與依賴注入的關系。

2. 舉個例子

假設有兩個元件: A.vue 和 B.vue,B 是 A 的子元件

// A.vue
export default {
  provide: {
    name: '浪裡行舟'
  }
}           
// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 浪裡行舟
  }
}           

可以看到,在 A.vue 裡,我們設定了一個 provide: name,值為 浪裡行舟,它的作用就是将 name 這個變量提供給它的所有子元件。而在 B.vue 中,通過

inject

注入了從 A 元件中提供的 name 變量,那麼在元件 B 中,就可以直接通過 this.name 通路這個變量了,它的值也是 浪裡行舟。這就是 provide / inject API 最核心的用法。

需要注意的是:provide 和 inject 綁定并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的對象,那麼其對象的屬性還是可響應的----vue 官方文檔

是以,上面 A.vue 的 name 如果改變了,B.vue 的 this.name 是不會改變的,仍然是 浪裡行舟。

3. provide 與 inject 怎麼實作資料響應式

一般來說,有兩種辦法:

  • provide 祖先元件的執行個體,然後在子孫元件中注入依賴,這樣就可以在子孫元件中直接修改祖先元件的執行個體的屬性,不過這種方法有個缺點就是這個執行個體上挂載很多沒有必要的東西比如 props,methods
  • 使用 2.6 最新 API Vue.observable 優化響應式 provide(推薦)

我們來看個例子:孫元件 D、E 和 F 擷取 A 元件傳遞過來的 color 值,并能實作資料響應式變化,即 A 元件的 color 變化後,元件 D、E、F 不會跟着變(核心代碼如下:)

Vue元件間通信6種方式
// A 元件
<div>
      <h1>A 元件</h1>
      <button @click="() => changeColor()">改變color</button>
      <ChildrenB />
      <ChildrenC />
</div>
......
  data() {
    return {
      color: "blue"
    };
  },
  // provide() {
  //   return {
  //     theme: {
  //       color: this.color //這種方式綁定的資料并不是可響應的
  //     } // 即A元件的color變化後,元件D、E、F不會跟着變
  //   };
  // },
  provide() {
    return {
      theme: this//方法一:提供祖先元件的執行個體
    };
  },
  methods: {
    changeColor(color) {
      if (color) {
        this.color = color;
      } else {
        this.color = this.color === "blue" ? "red" : "blue";
      }
    }
  }
  // 方法二:使用2.6最新API Vue.observable 優化響應式 provide
  // provide() {
  //   this.theme = Vue.observable({
  //     color: "blue"
  //   });
  //   return {
  //     theme: this.theme
  //   };
  // },
  // methods: {
  //   changeColor(color) {
  //     if (color) {
  //       this.theme.color = color;
  //     } else {
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
  //     }
  //   }
  // }           
// F 元件
<template functional>
  <div class="border2">
    <h3 :style="{ color: injections.theme.color }">F 元件</h3>
  </div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函數式元件取值不一樣
      default: () => ({})
    }
  }
};
</script>           

雖說 provide 和 inject 主要為高階插件/元件庫提供用例,但如果你能在業務中熟練運用,可以達到事半功倍的效果!

方法六、

$parent

$children

ref

  • ref

    :如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子元件上,引用就指向元件執行個體
  • $parent

    $children

    :通路父 / 子執行個體

需要注意的是:這兩種都是直接得到元件執行個體,使用後可以直接調用元件的方法或通路資料。我們先來看個用

ref

來通路元件的例子:

// component-a 子元件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}           
// 父元件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 彈窗
    }
  }
</script>           

不過,這兩種方法的弊端是,無法在跨級或兄弟間通信。

// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>           

我們想在 component-a 中,通路到引用它的頁面中(這裡就是 parent.vue)的兩個 component-b 元件,那這種情況下,就得配置額外的插件或工具了,比如 Vuex 和 Bus 的解決方案。

總結

常見使用場景可以分為三類:

  • 父子通信:

    父向子傳遞資料是通過 props,子向父是通過 events(

    $emit

    );通過父鍊 / 子鍊也可以通信(

    $parent

    $children

    );ref 也可以通路元件執行個體;provide / inject API;

    $attrs/$listeners

  • 兄弟通信:

    Bus;Vuex

  • 跨級通信:

    Bus;Vuex;provide / inject API、

    $attrs/$listeners

給大家推薦一個好用的 BUG 監控工具

,歡迎免費試用!

參考文章

關于Fundebug

專注于JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟體、百姓網等衆多品牌企業。歡迎大家

免費試用

繼續閱讀