Vue通信的幾種方式

組件之間的關(guān)系

組件之間通信, 主要存在于下面三種情況:

  • 父子組件之間(Index-A惊橱、Index-B...)
  • 兄弟組件之間(A-B)
  • 隔代關(guān)系組件之間(Index-C, Index-D)

那么具體他們之間如何進(jìn)行通信, 我們一一解答

首先先看下文件的目錄結(jié)構(gòu)

image.png

接下來看看具體的通信方式

1. props & $emit

1.1 父?jìng)髯?props

現(xiàn)在我們要從Index頁(yè)面給A頁(yè)面?zhèn)鬟f一個(gè)數(shù)組list

// index.vue
<template>
  <div>
    <A :list="list" />
  </div>
</template>
<script>

import A from "./components/A";
export default {
  name: "Index",
  components: {
   A
  },
  data() {
   return {
     list: ["html", "css", "js"],
   };
  },
},

</script>
// A.vue
<template>
  <div>
    <h2>Page A</h2>
    <ul v-for="(item, index) in list" :key="index">
      <li>{{ item }}</li>
    </ul>
  </div>
</template>
<script>
export default {
  name: "A",
  props: ["list"],
};
</script>

效果圖如下, 此時(shí)list數(shù)組以自上而下的一種方式從Index頁(yè)面?zhèn)鬟f給A組件, 且props只可以從上一級(jí)想下一級(jí)傳輸, 即所謂的單向數(shù)據(jù)流

image.png
1.2 子傳父 $emit

A組件想給Index頁(yè)面?zhèn)魉蛿?shù)據(jù)應(yīng)該如何操作? 關(guān)于子組件給父組件傳值, 一般都是通過一個(gè)事件搭配$emit進(jìn)行傳輸

// A.vue
<template>
  <div>
    <h2>Page A</h2>
    <ul v-for="(item, index) in list" :key="index">
       <!-- 定義事件 -->
      <li @click="onItemClick(item)">{{ item }}</li>
    </ul>
  </div>
</template>
<script>
export default {
  name: "A",
  props: ["list"],
  methods: {
    onItemClick(item) {
      this.$emit("on-item-click", `'${item}' from page A`);
    },
  },
};
</script>

父組件監(jiān)聽子組件上的事件名on-item-click

// index.vue
<template>
  <div>
    <!-- 監(jiān)聽 -->
    <A :list="list" @on-item-click="handleItemClick" />
  </div>
</template>

<script>
import A from "./components/A";

export default {
  name: "Index",
  components: {
    A,
  },
  data() {
    return {
      list: ["html", "css", "js"],
    };
  },
  methods: {
    handleItemClick(value) {
      console.log(`In page Index get ${value}`);
    },
  },
};
</script>

此時(shí), 點(diǎn)擊li元素的時(shí)候, 我們就可以在Index頁(yè)面獲取到A頁(yè)面?zhèn)鬟f過來的值

image.png

2. $children & $parent

  • $parent的類型為: Vue instance
  • $children的類型為: Array<Vue instance>, 需要注意 $children 并不保證順序彭则,也不是響應(yīng)式的

因?yàn)檫@兩個(gè)API拿到的都是vue實(shí)例, 所以可以訪問父組件或子組件身上變量, 方法等; 使用方式如下:

A頁(yè)面中定義了一個(gè)變量msg, 通過$parent獲取父組件中的數(shù)據(jù)

// A.vue
<template>
  <div>
    <h2>Page A</h2>
    <p>{{ msg }}</p>
    <ul v-for="(item, index) in $parent.list" :key="index">
      <li @click="onItemClick(item)">{{ item }}</li>
    </ul>
    <hr />
  </div>
</template>
<script>
export default {
  name: "A",
  data() {
    return {};
  },
  // 獲取Index中的list
  mounted() {
    console.log('A mounted', this.$parent.list);
  },
};
</script>

現(xiàn)在我們?cè)诟附M件中通過$children獲取到A頁(yè)面中的msg并修改

// Index.vue
<template>
  <div>
    <A :list="list" @on-item-click="handleItemClick" />
    <button @click="handleChange">change A's msg</button>
  </div>
</template>

<script>
import A from "./components/A";

export default {
  name: "Index",
  components: {
    A,
  },
  data() {
    return {
      list: ["html", "css", "js"],
    };
  },
  methods: {
    handleItemClick(value) {
      console.log(`In page Index get ${value}`);
    },
    handleChange() {
      console.log((this.$children[0].msg = "A's data is changed"));
    },
  },
};
</script>
image.png

3. ref

ref如果用在普通的DOM元素上, 引用指向的就是DOM元素本身; 如果使用在組件上, 引用指向的就是該組件的實(shí)例

// index.vue
<template>
  <div>
    <A ref="componentA" :list="list" @on-item-click="handleItemClick" />
    <p ref="p">p標(biāo)簽</p>
  </div>
</template>

<script>
import A from "./components/A";

export default {
  name: "Index",
  components: {
    A,
  },
  mounted() {
    console.log("componentA ref", this.$refs.componentA);
    console.log("p ref", this.$refs.p);
  },
};
</script>

下面是打印的結(jié)果

image.png

當(dāng) refv-for 一起使用的時(shí)候, $refs 得到的是一個(gè)數(shù)組

4. provide & inject

  • provideObject | () => Object
  • injectArray<string> | { [key: string]: string | Symbol | Object }

這對(duì)選項(xiàng)需要一起使用, 允許一個(gè)祖先組件向所有后代注入一個(gè)依賴, 不論層級(jí)有多深, 并在其上下游關(guān)系成立的時(shí)間里始終生效

我們現(xiàn)在來看一下具體的例子: Index -> A -> C

先在Index中提供數(shù)據(jù)

// index.vue
export default {
  name: "Index",
  components: {
    A,
  },
  provide: {
    name: "name from Index",
  },
  data() {
    return {
      list: ["html", "css", "js"],
    };
  },
  methods: {
    handleItemClick(value) {
      console.log(`In page Index get ${value}`);
    },
    handleChange() {
      console.log((this.$children[0].msg = "A's data is changed"));
    },
  },
};

然后在C組件中通過inject獲取值

// C.vue
export default {
  name: "C",
  inject: {
    value: "name",
    data: {
      from: "data1",
      default: "1",
    },
  },
  mounted() {
    // Index 中并沒有在 provide 中提供 data1 變量, 所以C組件會(huì)取默認(rèn)值
    console.log("C", this.value, this.data); // C  name from Index  1
  },
};

5. EventBus($emit, $on, $off)

這種方式是通過創(chuàng)建一個(gè)空的Vue實(shí)例作為事件總線, 用它來觸發(fā)事件, 監(jiān)聽事件, 解除事件, 從而實(shí)現(xiàn)任何組件之間的通信, 然后當(dāng)項(xiàng)目逐漸擴(kuò)大, 這種通信方式還是不建議選擇, 難以維護(hù)

使用EventBus來通信主要有以下幾個(gè)步驟

5.1 創(chuàng)建一個(gè)事件總線并將其導(dǎo)出
// event-bus.js

import Vue from "vue";
export const EventBus = new Vue();
5.2 發(fā)送一個(gè)事件

主要有Index厨钻、A牧嫉、B、C幾個(gè)頁(yè)面

// index.vue
<template>
  <div>
    <A />
    <B />
  </div>
</template>
// A.vue
<template>
  <div>
    <h2 @click="handleClick">Page A</h2>
    <C />
  </div>
</template>
<script>
import C from "./C";
// 引入
import { EventBus } from "../event-bus.js";

export default {
  name: "A",
  components: {
    C,
  },
  methods: {
    // 觸發(fā)事件
    handleClick() {
      EventBus.$emit("transfer-by-event-bus", "eventBus data");
    },
  },
};
</script>
5.3 監(jiān)聽事件, 接收數(shù)據(jù)
// B.vue
<template>
  <div>
    <h2>Page B</h2>
  </div>
</template>

<script>
import { EventBus } from "../event-bus";
export default {
  name: "B",
  mounted() {
    // 監(jiān)聽事件
    EventBus.$on("transfer-by-event-bus", value => console.log("B", value)); // B eventBus data
  },
};
</script>
// C.vue

<template>
  <h5>C</h5>
</template>

<script>
import { EventBus } from "../event-bus";
export default {
  name: "C",
  mounted() {
    EventBus.$on("transfer-by-event-bus", value => console.log("C", value)); // C eventBus data
  },
};
</script>
5.4 移除事件
import { eventBus } from 'event-bus.js'
EventBus.$off('transfer-by-event-bus', {})

6. $attrs & $listeners

6.1 $attrs

包含了父作用域中不作為 prop 被識(shí)別 (且獲取) 的 attribute 綁定 (classstyle 除外)泣港。當(dāng)一個(gè)組件沒有聲明任何 prop 時(shí)售躁,這里會(huì)包含所有父作用域的綁定 (classstyle 除外),并且可以通過 v-bind="$attrs" 傳入內(nèi)部組件——在創(chuàng)建高級(jí)別的組件時(shí)非常有用

我們先通過Index頁(yè)面向B組件傳遞name, sex, age三個(gè)值

// index.vue
<template>
  <div>
    <B :name="name" :sex="sex" :age="age" @on-click="handleClick" />
  </div>
</template>

<script>
import B from "./components/B";

export default {
  name: "Index",
  components: {
    B,
  },
  data() {
    return {
      name: "Lily",
      sex: "female",
      age: "20",
    };
  },
};
</script>

然后再B組件中我們打印一下$attrs, 看看能獲取什么值? 可以看見$attrs返回的是一個(gè)對(duì)象, 且鍵值對(duì)就是我們?cè)?code>Index頁(yè)面?zhèn)鹘oB組件的值

<template>
  <div>
    <h2>Page B</h2>
  </div>
</template>

<script>
export default {
  name: "B",
  mounted() {
    console.log(this.$attrs); // {name: "Lily", sex: "female", age: "20"}
  },

如果此時(shí)我們?cè)?code>B組件的props中接收一個(gè)變量, 看看有何變化? 打印出來的值不包含props獲取的name字段

<template>
  <div>
    <h2>Page B</h2>
  </div>
</template>

<script>
export default {
  name: "B",
  props: {
    name: String,
  },
  mounted() {
    console.log(this.$attrs); // {sex: "female", age: "20"}
  },

現(xiàn)在需要把這幾個(gè)變量再繼續(xù)傳給B組件的子組件D, 我們直接通過v-bind="$attrs"即可

// B.vue

<template>
  <div>
    <h2>Page B</h2>
    <D v-bind="$attrs" />
  </div>
</template>

<script>
import D from "./D";
export default {
  name: "B",
  components: {
    D,
  },
  props: {
    name: String,
  },
  mounted() {
    console.log(this.$attrs); // {sex: "female", age: "20"}
  },

D組件同樣獲取到了這幾個(gè)值

// D.vue

<template>
  <h5>D</h5>
</template>

<script>
export default {
  name: "D",
  mounted() {
    console.log(this.$attrs); // {sex: "female", age: "20"}
  },
};
</script>
6.2 $listeners

包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監(jiān)聽器墨微。它可以通過 v-on="$listeners" 傳入內(nèi)部組件——在創(chuàng)建更高層次的組件時(shí)非常有用

當(dāng)子組件需要調(diào)用父組件中的方法, 我們就可以通過$listeners來調(diào)用, 但前提是方法名必須在父組件中被定義

我們實(shí)操一下, 先在Index頁(yè)面的分別定義兩個(gè)事件

<template>
  <div>
    <B
      @click1="handeClick1"
      @click2="handeClick2"
    />
  </div>
</template>

<script>
import B from "./components/B";

export default {
  name: "Index",
  components: {
    B,
  },
  data() {
    return {};
  },
  methods: {
    handeClick1() {
      console.log("1");
    },
    handeClick2() {
      console.log("2");
    },
  },
};
</script>

子組件B調(diào)用方法如下, 現(xiàn)在我們分別點(diǎn)擊one, two兩個(gè)文本就可以分別打印出1, 2

// B.vue
<template>
  <div>
    <h2 @click="$listeners.click1">one</h2>
    <h2 @click="$listeners.click2">two</h2>
  </div>
</template>

<script>
export default {
  name: "B",
  data() {
    return {};
  },
};
</script>

如果再向下傳遞給B的子組件D也是沒有問題的, 我們只需要將$listeners傳下就可以了

// B.vue

<template>
  <div>
    <h2 @click="$listeners.click1">one</h2>
    <h2 @click="$listeners.click2">two</h2>
    <D v-on="$listeners" />
  </div>
</template>

D組件也像B組件那樣調(diào)用事件即可

// D.vue

<template>
  <h5 @click="$listeners.click1">D</h5>
</template>

7. Vuex

Vuex想必大家應(yīng)該很熟悉, 它是一個(gè)專為Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式, 它讓開發(fā)者能夠聚焦于數(shù)據(jù)的更新而不是數(shù)據(jù)的傳遞

Vuex主要有以下幾個(gè)模塊

  • state: 用于數(shù)據(jù)的存儲(chǔ), 是store中的唯一數(shù)據(jù)源
  • getters: 同vue中的計(jì)算屬性, 基于state數(shù)據(jù)進(jìn)行二次包裝來獲取符合條件的數(shù)據(jù)
  • mutations: 處理同步事件, 是唯一更改 store 中狀態(tài)的方法
  • actions: 可包含異步操作, 用于提交mutation, 不可直接變更狀態(tài)
  • modules: 類似于命名空間, 用于項(xiàng)目中將各個(gè)模塊的狀態(tài)分開定義和操作, 便于維護(hù)

8. 總結(jié)

我們現(xiàn)在就開頭說的三種場(chǎng)景再進(jìn)行一次總結(jié), 但是我們還是需要根據(jù)當(dāng)下的場(chǎng)景選擇合適的通信方式~

  • 父子組件之間: props & $emit, $chilren & $parent,ref, EventBus, provide & inject, Vuex
  • 兄弟組件之間: Vuex, EventBus
  • 隔代關(guān)系組件之間: EventBus, $attrs & $listeners, provide & inject, Vuex
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末道媚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子翘县,更是在濱河造成了極大的恐慌最域,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锈麸,死亡現(xiàn)場(chǎng)離奇詭異羡宙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掐隐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門狗热,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虑省,你說我怎么就攤上這事匿刮。” “怎么了探颈?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵熟丸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我伪节,道長(zhǎng)光羞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任怀大,我火速辦了婚禮纱兑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘化借。我一直安慰自己潜慎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铐炫,像睡著了一般垒手。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倒信,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天科贬,我揣著相機(jī)與錄音,去河邊找鬼鳖悠。 笑死榜掌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的竞穷。 我是一名探鬼主播唐责,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼鳞溉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼瘾带!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起熟菲,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤看政,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后抄罕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體允蚣,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年呆贿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嚷兔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡做入,死狀恐怖冒晰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竟块,我是刑警寧澤壶运,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站浪秘,受9級(jí)特大地震影響蒋情,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耸携,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一棵癣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夺衍,春花似錦浙巫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渊抄。三九已至,卻和暖如春丧裁,著一層夾襖步出監(jiān)牢的瞬間护桦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工煎娇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留二庵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓缓呛,卻偏偏與公主長(zhǎng)得像催享,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哟绊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言 組件是 vue.js最強(qiáng)大的功能之一因妙,而組件實(shí)例的作用域是相互獨(dú)立的,這就意味著不同組件之間的數(shù)據(jù)無法相互引...
    用技術(shù)改變世界閱讀 2,167評(píng)論 1 3
  • Vue2.0 傳值方式: 在Vue的框架開發(fā)的項(xiàng)目過程中票髓,經(jīng)常會(huì)用到組件來管理不同的功能攀涵,有一些公共的組件會(huì)被提取...
    陀飛輪h閱讀 378評(píng)論 0 0
  • 面試的時(shí)候,也算是城⒐担考的一道題目了以故,而且,在日常的開發(fā)中裆操,對(duì)于組件的封裝怒详,尤其是在ui組件庫(kù)中,會(huì)用到很多踪区,下面昆烁,...
    輝夜真是太可愛啦閱讀 475評(píng)論 0 1
  • Vue組件通信的幾種方式【轉(zhuǎn)】 組件通信主要有以下幾種方式:props,$emit和$on,vuex,$attrs...
    彩云_789d閱讀 1,136評(píng)論 0 0
  • 摘要: 總有一款合適的通信方式。 作者:浪里行舟 Fundebug經(jīng)授權(quán)轉(zhuǎn)載朽缴,版權(quán)歸原作者所有善玫。 前言 組件是 v...
    Fundebug閱讀 15,574評(píng)論 3 57