$attrs 與 $listeners 進行「巢狀元件」通訊

舞動乾坤發表於2019-02-27

轉自Vue案例引發的「巢狀元件」通訊的簡單方式

我們都知道 Vue 是採用元件化開發的模式,元件化的優勢在於相對獨立,易於維護,可複用。你可以把專案看成許多元件的組合而成。

既然專案中存在很多的元件,而且又是相對獨立的,但元件間肯定是存在資料的傳遞互動。Vue中給我提供比較多的方式去進行元件間的互動通訊。

這篇文章不打算詳盡元件之間的通訊,而是說說利用 $attrs$listeners 進行「巢狀元件」的通訊。

可以想象一下專案中元件與元件的關係無外乎這麼幾種:父子,兄弟,祖孫(巢狀)。

  1. 父子元件:父元件通過 props 向下傳遞子元件資料,子元件通過事件向上傳送父元件訊息。或者也可以通過 ref 屬性、$parent$children等方法獲取資料和事件。
  2. 兄弟元件:可以通過共同的父元件作為橋樑進行通訊,也可以利用全域性事件 eventBus 或者比較複雜的 Vuex。

一圖勝千言

$attrs 與 $listeners 進行「巢狀元件」通訊

通過上圖,我們可以瞭解常見的元件通訊方式。但實際的開發專案中可能並沒有這麼簡單,最近在做專案時遇到巢狀元件的情況,比如「元件A」包含「元件B」,「元件B」包含「元件C」。

那「元件A」與「元件C」如何通訊就是值得我們商榷的問題,是利用 Vuex 還是利用其他方式呢?

首先 Vuex 是優秀的狀態管理工具,對於複雜而又龐大的系統而言使用 Vuex 再好不過。

但如果我們的系統相對簡單,並且「元件A」與「元件C」之間只是進行簡單的資料傳遞,似乎引入 Vuex 並不是一個好的選擇,相反會帶來複雜度的上升。

不過 Vue 在 2.4.0 版本新增了 2 個屬性$attrs$listeners,使用它們進行巢狀元件(祖孫)的通訊是一個不錯的選擇,接下來我們就看看它們是什麼,以及如何使用。

1.$attrs

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

2.$listeners

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

簡單來說:$attrs$listeners 是兩個「物件」,$attrs 裡存放的是父元件中繫結的非 Props 屬性,$listeners裡存放的是父元件中繫結的非原生事件。

在不明白的話看個案例

//componentA
<template>
  <div class="component-a">
    <component-b :name="name" :tag="tag" :age="age" @click.native="say" @mouseover="sing"></component-b>
  </div>
</template>
<script>
import componentB from "./ComponentB";
export default {
  name: "componentA",
  components: { componentB },
  data() {
    return {
      name: "六哥",
      tag: "帥",
      age: 18
    };
  },
  methods: {
    say() {},
    sing() {}
  }
};
</script>

//componentB
<template>
  <div class="component-b"></div>
</template>
<script>
export default {
  name: "ComponentB",
  props: {
    age: Number
  },
  mounted() {
    console.log(this.$attrs, this.$listeners);
    //{name: "六哥", tag: "帥"}, {mouseover: ƒ}
  }
};
</script>
複製程式碼

明白這兩個屬性之後,我們來看看如何利用它進行祝祖孫元件之間的傳遞。假如有三個元件分別是「元件A」包含「元件B」,「元件B」包含「元件C」。

在上面案例的基礎上,我們新增「元件C」,並對「元件B」進行改善。

//componentB
<template>
  <div class="component-b">
    <component-c v-bind="$attrs" v-on="$listeners"></component-c>
  </div>
</template>
<script>
import ComponentC from "./ComponentC";
export default {
  name: "ComponentB",
  components: { ComponentC }
};
</script>

//componentC
<template>
  <div class="component-c"></div>
</template>
<script>
export default {
  name: "ComponentC",
  mounted() {
    console.log(this.$attrs, this.$listeners);
    //{name: "六哥", tag: "帥", age: 18} {mouseover: ƒ}
  }
};
</script>
複製程式碼

可以看到我們利用$attrs$listeners在不引入額外的工具或者全域性屬性,就可以實現從「元件A」到「元件C」之間的資料通訊。如果有相同場景的小夥伴,趕緊用起來吧。

另外一點,這裡需要注意我們使用了非 Props 特性,Vue 中元件如果接受非 Props 屬性的時候,會把屬性渲染到 HTML 的原生標籤上。

例如:

<component-b :name="name" :tag="tag"></component-b>
複製程式碼

渲染後的結果會是

<div class="component-b" name="六哥" tag="帥"></div>
複製程式碼

顯然這不是我們想要的,我們的原生標籤不需要 「name」與 「tag」這兩個屬性。那如何避免的呢?很簡單,你可以在元件的選項中設定 inheritAttrs: false。

<script>
import ComponentC from "./ComponentC";
export default {
  inheritAttrs: false,
  name: "ComponentB",
  components: { ComponentC }
};
</script>
複製程式碼

以上就是我們今天要說的 $attrs$listeners.

相關文章