vue 元件通訊總結 (非vuex和Event Bus)

黑色瓶子發表於2019-02-05

方式一覽

  1. props && emit
  2. v-model
  3. $children && $parent
  4. $listeners && $attrs
  5. .sync

prop && emit 方式

父元件通過 props 傳遞資料給子元件,子元件通過 emit 傳送事件傳遞資料給父元件。這是最常用的父子元件通訊方式,符合單向資料流,即子元件不能直接修改 props, 而是必須通過傳送事件的方式告知父元件修改資料。由於是常用的方式,在這也不多囉嗦了。

v-model 方式

v-model實現的通訊其本質上還是上面的propsemit方式,使用v-model更像是一種語法糖。文件介紹

先舉個栗子:

// 這是父元件
<template>
  <div>
    <child v-model="msg"></child>
    <p>{{msg}}</p>
  </div>
</template>

<script>
import child from "../components/Child";
export default {
  data() {
    return {
      msg: "hello"
    };
  },
  components: { child }
};
</script>

複製程式碼
// 這是子元件
<template>
  <div>
      <input :value="value" @input="$emit('input',$event.target.value)">
  </div>
</template>

<script>
export default {
  props: ["value"]
};
</script>
複製程式碼

父元件使用子元件時,使用v-model繫結父元件msg資料,這會在子元件裡解析成名為 value 的 prop 和名為 input 的事件,所以子元件裡的props選項裡必須寫成value,在$emit事件裡也需寫成input事件。此時當你在子元件輸入時,就會改變父元件的msg值。

使用 model 選項自定義 props 和 event

上面說了,props選項裡必須寫value,事件也必須是input。這是預設情況下的解析,其實我們也可以自定義 props 和 event,使用model選項,文件介紹。文件中以核取方塊為例,修改 props 和 event:

model: {
    prop: 'checked',
    event: 'change'
}
複製程式碼

$children && $parent 方式

這兩個是vue提供的api,見名知意,在父元件裡使用 $children 訪問子元件,在子元件裡使用$parent訪問父元件。

舉個簡單栗子:

// 這是子元件

<template>
  <div>
     {{$parent.msg}}   // 子元件顯示父元件資料
  </div>
</template>

<script>
export default {
  data() {
    return {
      child_msg: "我是子元件資料"
    };
  },
  mounted() {
    this.$parent.test(); // 子元件執行父元件方法
  }
};
</script>
複製程式碼
// 這是父元件

<template>
  <div>
    <child/>
  </div>
</template>

<script>
import child from "../components/Child";
export default {
  data() {
    return {
      msg: "我是父元件的資料"
    };
  },
  components: { child },
  methods: {
    test() {
      console.log("我是父元件的方法,被執行");
    }
  },
  mounted() {
    console.log(this.$children[0].child_msg); // 執行子元件方法
  }
};
</script>
複製程式碼

【注意】 $children 是陣列,所以當只有一個子元件時,使用[0]獲取。當有多個子元件時,它並不保證順序,也不是響應式的。

$listeners 方式

初看此api的定義,我也是似懂非懂:

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

文件這裡也描述了它的使用方法: 文件介紹

在檢視一些部落格時,要麼拿官方例子,要麼一大堆介紹,其實我看的也是一臉懵逼。後來自己慢慢試著用了下,也大概明白它是幹嘛的。我的理解:在多層巢狀元件的業務中,使用$listeners可以使用更少的程式碼來完成事件通訊。

還是以程式碼來說明,如下圖,我們來實現元件B 到 父元件 的通訊,

vue 元件通訊總結 (非vuex和Event Bus)

一般巢狀層級太多時,我們可能就會考慮vuex,但只傳遞資料,而不做中間處理,有點大材小用,所以如上圖這樣的,我們可能還是使用emit方式來通訊,無非多傳一層,多寫點程式碼。那麼現在,有了$listeners,我們可以更方便的來實現,我儘量用最少的程式碼來實現下:

就從最下面的B元件開始,它有一個按鈕,點選時觸發例項上的事件getFromB

// 元件B

<template>
  <div>
    <button @click="handleClick">B元件按鈕</button>
  </div>
</template>

<script>
export default {
  methods: {
    handleClick() {
      this.$emit("getFromB");
    }
  }
};
</script>
複製程式碼

A元件 包裹 B元件,相當於是父元件與B元件的中轉站,在不用$listeners時,我們可能會在這裡再觸發一個事件,現在不需要這樣了,我們這樣:

// 元件A

<template>
  <div>
    <child-b v-on="$listeners" />
  </div>
</template>

<script>
import childB from "../components/ChildB";
export default {
  components: {
    childB
  },
  mounted() {
    console.log(this.$listeners);
  }
};
</script>
複製程式碼

只需要加一句v-on="$listeners"即可。好奇的我們也可以 mounted 時列印一下$listeners

父元件,顯而易見,我們直接繫結getFromB事件即可:

// 父元件

<template>
  <div>
   <child-a v-on:getFromB="fromB"/>
  </div>
</template>

<script>
import childA from "../components/ChildA";
export default {
  components: { childA },
  methods: {
    fromB() {
      console.log("B元件觸發");
    }
  }
};
</script>
複製程式碼

這就是$listeners的簡單用法,說到這裡,你應該意識到,當元件巢狀很多層時,不借助 vuex,我們也可以較方便地實現通訊了。

說到這裡,我還要提一個api,就是$attrs。它與$listeners的關係就好比 props 與 emit 的關係,用來向底層元件傳遞屬性。先貼上它的定義:

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

我們回想下,如果使用 props 向孫元件傳遞資料時,在中間元件裡,我們是要一層層使用 props 選項來接收,然後再傳遞的。那麼$attrs的作用就是在沒到目標子元件時,不使用props接收資料,直到到達需要資料的元件時,再使用props接收。

在我看別的部落格時,都是這兩個api一起說的,程式碼比較多,為了清晰,我把上面程式碼多餘的程式碼刪掉,只演示$attrs的使用:

父元件傳遞一個屬性toB,意為是給B元件用的:

// 父元件

<template>
  <div>
   <child-a toB="hello"/>
  </div>
</template>

<script>
import childA from "../components/ChildA";
export default {
  components: { childA }
};
</script>
複製程式碼

A元件使用v-bind="$attrs"即可,不需要 props 接收,實際上也不可以接收,看定義

// 元件 A

<template>
  <div>
     <child-b v-bind="$attrs" />
  </div>
</template>

<script>
import childB from "../components/ChildB";
export default {
  components: { childB }
};
</script>
複製程式碼

B元件是我們的最後子元件,它用到toB屬性,所以使用 props 選項接收了

<template>
  <div>
    <p>父元件傳來資料:{{toB}}</p>
  </div>
</template>

<script>
export default {
  props: ["toB"]
};
</script>
複製程式碼

從這個簡單的例子,我們可以知道,當元件巢狀層級很多時,屬性傳遞變得不要太方便。最後還要提一個inheritAttrs選項,它一般配合$attrs使用,這裡我就不再多說了。文件介紹

.sync 方式

此方法其實用的也不少,它在 Vue 1.x 裡的作用是對一個 prop 進行“雙向繫結“。但在 Vue 2 之後是隻允許單向資料流的,所以現在即使它看起來像是真正的“雙向繫結”,本質上也只是作為一個編譯時的語法糖存在而已。

舉個計數器的例子:

// 父元件

<template>
  <div>
    {{num}}
   <child-a :count.sync="num" />
  </div>
</template>

<script>
import childA from "../components/ChildA";
export default {
  data() {
    return {
      num: 0
    };
  },
  components: { childA }
};
</script>
複製程式碼
// 子元件

<template>
  <div>
     <div @click="handleAdd">ADD</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: this.count
    };
  },
  props: ["count"],
  methods: {
    handleAdd() {
      this.$emit("update:count", ++this.counter);
    }
  }
};
</script>
複製程式碼

嗯,看起來似乎更有逼格。

結語

這麼看下來,除了$children$parent 是直接獲取的,其他都跟 props 和 emit 息息相關。具體怎麼用,自己看著辦唄。

最後,新年快樂!

相關文章