Vue 元件資料通訊方案總結

政採雲前端團隊發表於2019-08-27

原創不易,希望能關注下我們,再順手點個贊~~

本文首發於政採雲前端團隊部落格: Vue 元件資料通訊方案總結

季節.png

背景

初識 Vue.js ,瞭解到元件是 Vue 的主要構成部分,但元件內部的作用域是相對獨立的部分,元件之間的關係一般如下圖:

Vue 元件資料通訊方案總結

元件 A 與元件 B 、C 之間是父子元件,元件 B 、C 之間是兄弟元件,而元件 A 、D 之間是隔代的關係。

那麼對於這些不同的關係,本文主要分享了他們之間可以採用的幾種資料通訊方式,例如 Props 、$emit / $on 、Vuex 等,大家可以根據自己的使用場景可以選擇適合的使用方式。

一、 Prop / $emit

1、Prop 是你可以在元件上註冊的一些自定義特性。當一個值傳遞給一個 Prop 特性的時候,它就變成了那個元件例項的一個屬性。父元件向子元件傳值,通過繫結屬性來向子元件傳入資料,子元件通過 Props 屬性獲取對應資料

// 父元件
<template>
  <div class="container">
    <child :title="title"></child>
  </div>
</template>

<script>
import Child from "./component/child.vue";
export default {
  name: "demo",
  data: function() {
    return {
      title: "我是父元件給的"
    };
  },
  components: {
    Child
  },
};
</script>
複製程式碼
// 子元件
<template>
  <div class="text">{{title}}</div>
</template>

<script>
export default {
  name: 'demo',
  data: function() {},
  props: {
    title: {
      type: String
    }
  },
};
</script>
複製程式碼

2、$emit 子元件向父元件傳值(通過事件形式),子元件通過 $emit 事件向父元件傳送訊息,將自己的資料傳遞給父元件。

Vue 元件資料通訊方案總結

// 父元件
<template>
  <div class="container">
    <div class="title">{{title}}</div>
    <child @changeTitle="parentTitle"></child>
  </div>
</template>

<script>
import Child from "./component/child.vue";

export default {
  name: "demo",
  data: function() {
    return {
      title: null
    };
  },
  components: {
    Child
  },
  methods: {
    parentTitle(e) {
      this.title = e;
    }
  }
};
</script>
複製程式碼
// 子元件
<template>
  <div class="center">
    <button @click="childTitle">我給父元件賦值</button>
  </div>
</template>

<script>
export default {
  name: 'demo',
  data() {
    return {
      key: 1
    };
  },
  methods: {
    childTitle() {
      this.$emit('changeTitle', `我給父元件的第${this.key}次`);
      this.key++;
    }
  }
};
</script>
複製程式碼

小總結:常用的資料傳輸方式,父子間傳遞。

二、 $emit / ​$on

這個方法是通過建立了一個空的 vue 例項,當做 $emit 事件的處理中心(事件匯流排),通過他來觸發以及監聽事件,方便的實現了任意元件間的通訊,包含父子,兄弟,隔代元件。

Vue 元件資料通訊方案總結

// 父元件
<template>
  <div class="container">
    <child1 :Event="Event"></child1>
    <child2 :Event="Event"></child2>
    <child3 :Event="Event"></child3>
  </div>
</template>

<script>
import Vue from "vue";

import Child1 from "./component/child1.vue";
import Child2 from "./component/child2.vue";
import Child3 from "./component/child3.vue";

const Event = new Vue();

export default {
  name: "demo",
  data: function() {
    return {
      Event: Event
    };
  },
  components: {
    Child1,
    Child2,
    Child3
  },
};
</script>
複製程式碼
// 子元件1
<template>
  <div class="center">
    1.我的名字是:{{name}}
    <button @click="send">我給3元件賦值</button>
  </div>
</template>

<script>
export default {
  name: "demo1",
  data() {
    return {
      name: "政採雲"
    };
  },
  props: {
    Event: Object
  },
  methods: {
    send() {
      this.Event.$emit("message-a", this.name);
    }
  }
};
</script>
複製程式碼
// 子元件2
<template>
  <div class="center">
    2.我的年齡是:{{age}}歲
    <button @click="send">我給3元件賦值</button>
  </div>
</template>

<script>
/* eslint-disable */
export default {
  name: "demo2",
  data() {
    return {
      age: "3"
    };
  },
  props: {
    Event: Object
  },
  methods: {
    send() {
      this.Event.$emit("message-b", this.age);
    }
  }
};
</script>
複製程式碼
// 子元件3
<template>
  <div class="center">我的名字是{{name}},今年{{age}}歲</div>
</template>

<script>
export default {
  name: 'demo3',
  data() {
    return {
      name: '',
      age: ''
    };
  },
  props: {
    Event: Object
  },
  mounted() {
    this.Event.$on('message-a', name => {
      this.name = name;
    });
    this.Event.$on('message-b', age => {
      this.age = age;
    });
  },
};
</script>
複製程式碼

小總結:巧妙的在父子,兄弟,隔代元件中都可以互相資料通訊。

三、 Vuex

Vuex 是一個專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式儲存管理應用的所有元件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

Vue 元件資料通訊方案總結

Vuex實現了一個單項資料流,通過建立一個全域性的 State 資料,元件想要修改 State 資料只能通過 Mutation 來進行,例如頁面上的操作想要修改 State 資料時,需要通過 Dispatch (觸發 Action ),而 Action 也不能直接運算元據,還需要通過 Mutation 來修改 State 中資料,最後根據 State 中資料的變化,來渲染頁面。

Vue 元件資料通訊方案總結

// index.js
import Vue from 'vue';
import Tpl from './index.vue';
import store from './store';

new Vue({
  store,
  render: h => h(Tpl),
}).$mount('#app');
複製程式碼
// store
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    reduce(state) {
      state.count--;
    }
  },
  actions: {
    actIncrement({ commit }) {
      commit('increment');
    },
    actReduce({ commit }) {
      commit('reduce');
    }
  },
  getters: {
    doubleCount: state => state.count*2
  }
});

export default store;
複製程式碼
// vue檔案
<template>
  <div class="container">
    <p>我的count:{{count}}</p>
    <p>doubleCount:{{doubleCount}}</p>
    <button @click="this.actIncrement">增加</button>
    <button @click="this.actReduce">減少</button>
  </div>
</template>

<script>
import { mapGetters, mapActions, mapState } from "vuex";

export default {
  name: "demo",
  data: function() {
    return {};
  },
  components: {},
  props: {},
  computed: {
    ...mapState(["count"]),
    ...mapGetters(["doubleCount"])
  },
  methods: {
    ...mapActions(["actIncrement", "actReduce"])
  }
};
</script>
複製程式碼

Vuex 中需要注意的點:

Mutation :是修改State資料的唯一推薦方法,且只能進行同步操作。

Getter :Vuex 允許在Store中定義 "Getter"(類似於 Store 的計算屬性)。Getter 的返回值會根據他的依賴進行快取,只有依賴值發生了變化,才會重新計算。

本段只是簡單介紹了一下 Vuex 的執行方式,更多功能例如 Module 模組請參考官網

小總結:統一的維護了一份共同的 State 資料,方便元件間共同呼叫。

四、 $attrs / $listeners

Vue 元件間傳輸資料在 Vue2.4 版本後有了新方法。除了 Props 外,還有了 $attrs /​ $listeners。

  • $attrs:包含了父作用域中不作為 Prop 被識別 (且獲取) 的特性繫結 (ClassStyle 除外)。當一個元件沒有宣告任何 Prop 時,這裡會包含所有父作用域的繫結 (ClassStyle 除外),並且可以通過 v-bind="$attrs" 傳入內部元件——在建立高階別的元件時非常有用。
  • $listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部元件

下面來看個例子

Vue 元件資料通訊方案總結

// 父元件
<template>
  <div class="container">
    <button style="backgroundColor:lightgray" @click="reduce">減dd</button>
    <child1 :aa="aa" :bb="bb" :cc="cc" :dd="dd" @reduce="reduce"></child1>
  </div>
</template>

<script>
import Child1 from './component/child1.vue';
export default {
  name: 'demo',
  data: function() {
    return {
      aa: 1,
      bb: 2,
      cc: 3,
      dd: 100
    };
  },
  components: {
    Child1
  },
  methods: {
    reduce() {
      this.dd--;
    }
  }
};
</script>
複製程式碼
// 子元件1
<template>
  <div>
    <div class="center">
      <p>aa:{{aa}}</p>
      <p>child1的$attrs:{{$attrs}}</p>
      <button @click="this.reduce1">元件1減dd</button>
    </div>
    <child2 v-bind="$attrs" v-on="$listeners"></child2>
  </div>
</template>

<script>
import child2 from './child2.vue';
export default {
  name: 'demo1',
  data() {
    return {};
  },
  props: {
    aa: Number
  },
  components: {
    child2
  },
  methods: {
    reduce1() {
      this.$emit('reduce');
    }
  }
};
</script>
複製程式碼
// 子元件2
<template>
  <div>
    <div class="center">
      <p>bb:{{bb}}</p>
      <p>child2的$attrs:{{$attrs}}</p>
      <button @click="this.reduce2">元件2減dd</button>
    </div>
    <child3 v-bind="$attrs"></child3>
  </div>
</template>

<script>
import child3 from './child3.vue';
export default {
  name: 'demo1',
  data() {
    return {};
  },
  props: {
    bb: Number
  },
  components: {
    child3
  },
  methods: {
    reduce2() {
      this.$emit('reduce');
    }
  }
};
</script>
複製程式碼
// 子元件3
<template>
  <div class="center">
    <p>child3的$attrs:{{$attrs}}</p>
  </div>
</template>

<script>
export default {
  name: 'demo3',
  data() {
    return {};
  },
  props: {
    dd: String
  },
};
</script>
複製程式碼

簡單來說,$attrs 裡存放的是父元件中繫結的非 props 屬性,​$listeners 裡面存放的是父元件中繫結的非原生事件。

小總結:當傳輸資料、方法較多時,無需一一填寫的小技巧。

五、 Provider / Inject

Vue2.2 版本以後新增了這兩個 API, 這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在其上下游關係成立的時間裡始終生效。 簡單來說,就是父元件通過 Provider 傳入變數,任意子孫元件通過 Inject 來拿到變數。

// 父元件
<template>
  <div class="container">
    <button @click="this.changeName">我要改名字了</button>
    <p>我的名字:{{name}}</p>
    <child1></child1>
  </div>
</template>

<script>
import Child1 from './component/child1.vue';
export default {
  name: 'demo',
  data: function() {
    return {
      name: '政採雲'
    };
  },
  // provide() {
  //   return {
  //     name: this.name //這種繫結方式是不可響應的
  //   };
  // },
  provide() {
    return {
      obj: this
    };
  },
  components: {
    Child1
  },
  methods: {
    changeName() {
      this.name = '政採雲前端';
    }
  }
};
</script>
複製程式碼
// 子元件
<template>
  <div>
    <div class="center">
      <!-- <p>子元件名字:{{name}}</p> -->
      <p>子元件名字:{{this.obj.name}}</p>
    </div>
    <child2></child2>
  </div>
</template>

<script>
import child2 from './child2.vue';

export default {
  name: 'demo1',
  data() {
    return {};
  },
  props: {},
  // inject: ["name"],
  inject: {
    obj: {
      default: () => {
        return {};
      }
    }
  },
  components: {
    child2
  },
};
</script>
複製程式碼

需要注意的是:Provide 和 Inject 繫結並不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的物件,那麼其物件的屬性還是可響應的。

所以,如果採用的是我程式碼中註釋的方式,父級的 name 如果改變了,子元件this.name 是不會改變的,仍然是 政採雲,而當採用程式碼中傳入一個監聽物件,修改物件中屬性值,是可以監聽到修改的。

Provider / Inject 在專案中需要有較多公共傳參時使用還是頗為方便的。

小總結:傳輸資料父級一次注入,子孫元件一起共享的方式。

六、 $parent / $children & $refs

  • $parent / $children:指定已建立的例項之父例項,在兩者之間建立父子關係。子例項可以用 this.$parent 訪問父例項,子例項被推入父例項的 $children 陣列中。
  • $refs:一個物件,持有註冊過 ref 特性 的所有 DOM 元素和元件例項。ref 被用來給元素或子元件註冊引用資訊。引用資訊將會註冊在父元件的 $refs 物件上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子元件上,引用就指向元件。
// 父元件
<template>
  <div class="container">
    <p>我的title:{{title}}</p>
    <p>我的name:{{name}}</p>
    <child1 ref="comp1"></child1>
    <child2 ref="comp2"></child2>
  </div>
</template>

<script>
import Child1 from './component/child1.vue';
import Child2 from './component/child2.vue';
export default {
  name: 'demo',
  data: function() {
    return {
      title: null,
      name: null,
      content: '就是我'
    };
  },
  components: {
    Child1,
    Child2
  },
  mounted() {
    const comp1 = this.$refs.comp1;
    this.title = comp1.title;
    comp1.sayHello();
    this.name = this.$children[1].title;
  },
};
</script>
複製程式碼
// 子元件1-ref方式
<template>
  <div>
    <div class="center">我的父元件是誰:{{content}}</div>
  </div>
</template>

<script>
export default {
  name: 'demo1',
  data() {
    return {
      title: '我是子元件',
      content: null
    };
  },
  mounted() {
    this.content = this.$parent.content;
  },
  methods: {
    sayHello() {
      window.alert('Hello');
    }
  }
};
</script>
複製程式碼
// 子元件2-children方式
<template>
  <div>
    <div class="center"></div>
  </div>
</template>

<script>
export default {
  name: 'demo1',
  data() {
    return {
      title: '我是子元件2'
    };
  },
};
</script>
複製程式碼

通過例子可以看到這兩種方式都可以父子間通訊,而缺點也很統一,就是都不能跨級以及兄弟間通訊。

小總結:父子元件間共享資料以及方法的便捷實踐之一。

總結

元件間不同的使用場景可以分為 3 類,對應的通訊方式如下:

  • 父子通訊:Props / $emit,$emit / $on,Vuex,$attrs / ​$listeners,provide/inject,$parent / $children&$refs
  • 兄弟通訊:$emit / $on,Vuex
  • 隔代(跨級)通訊:$emit / ​$on,Vuex,provide / inject,$attrs / $listeners

大家可以根據自己的使用場景選擇不同的通訊方式,當然還是都自己寫寫程式碼,試驗一把來的印象深刻嘍。

招賢納士(Recruitment)

招人,前端,隸屬政採雲前端大團隊(ZooTeam),50 餘個小夥伴正等你加入一起浪 [壞笑] 如果你想改變一直被事折騰,希望開始能折騰事;如果你想改變一直被告誡需要多些想法,卻無從破局;如果你想改變你有能力去做成那個結果,卻不需要你;如果你想改變你想做成的事需要一個團隊去支撐,但沒你帶人的位置;如果你想改變既定的節奏,將會是“5年工作時間3年工作經驗”;如果你想改變本來悟性不錯,但總是有那一層窗戶紙的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望參與到隨著業務騰飛的過程,親手參與一個有著深入的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我覺得我們該聊聊。任何時間,等著你寫點什麼,發給 ZooTeam@cai-inc.com

推薦閱讀(Recommend)

看完這篇,你也能把 React Hooks 玩出花

自動化 Web 效能優化分析方案

CSS 層疊上下文(Stacking Context)

相關文章