$parent/$children的使用場景 -- vue組件通信系列

語言: CN / TW / HK

vue 組件的數據通信方式很多,本篇着重講$parent/$children,神助是$broadcast/$dispatch

$parent/$children的常用場景:封裝嵌套組件時,直接使用長輩或者子孫組件的方法,該方法並不改變數據,常常結合$broadcast/$dispatch使用。

什麼是$parent/$children

先問一句,div標籤,其父元素和子元素是誰?

這其實沒有答案。

父元素和子元素是誰,取決於運行時div的位置。

試着説説,以下div的父元素和子元素。

<body>
  <div id="div1">
    <main>
      <div id="div2">
        <h1>一級標題<h1>
        <p>段落<p>
      </div>
    <main>
  </div>
</body>
複製代碼

div1:

  • 父元素是,body
  • 子元素是,main。注意是子元素是指直接後代。

div2:

  • 父元素是,main
  • 子元素是,h1和p。注意是子元素是指直接後代。

總結下:

  • 元素本身沒有父元素和子元素,而是在運行時,每個元素實例的位置,決定其父元素和子元素
  • 每個元素實例有且只有一個父元素(頂級元素實例沒有哈)
  • 但每個元素實例可能沒有子元素,可能有一個,也可能有多個,所以一般children是數組,沒有的時候是空數組
  • js 運行時,若添加或者刪除元素實例,那些發生位置變化的元素實例們,父元素和子元素也會發生變化。

正文來了!!!組件也是一樣滴!!!

因為本來組件就是模仿元素的嘛!!!

把上面的元素成組件即可!

  • 組件本身沒有父組件和子組件,而是在運行時,每個組件實例的位置,決定其父組件和子組件
  • 每個組件實例有且只有一個父組件(頂級組件實例沒有哈)
  • 但每個組件實例可能沒有子組件,可能有一個,也可能有多個,所以一般children是數組,沒有的時候是空數組
  • js 運行時,若添加或者刪除組件實例,那些發生位置變化的組件實例們,父組件和子組件也會發生變化。

其實屬性和事件這種機制,也可以類比元素理解,當然這是後話。

舉例説明$parent/$children

寫一個頁面組件,裏面放些組件,打印下$parent/$children

parent

<template lang="pug">
//- 頁面組件
div
  list-item
  list-item
</template>
<script>
import ListItem from "@/components/ListItem";
export default {
  name: "List",
  components: { ListItem },
  mounted() {
    console.log("頁面的$parent", this.$parent);
    console.log("頁面的$children", this.$children);
  }
};
</script>
複製代碼

其實還能看到,渲染的時候先子組件的mounted,然後再是自己的mounted.
要是想在子組件裏獲取父組件的元素之類的,必須使用nextTick

$dispatch

在使用element-ui的時候,有個el-form,大約是這麼用的:

<template lang="pug">
el-form
  el-form-item
    el-input
</template>
複製代碼

假設el-input想要執行el-form上的方法,就會這樣this.$parent.$parent.methodXx(),更多層級可能更復雜,於是$dispatch就誕生了。

// main.js
// 向上某個組件,派發事件
Vue.prototype.$dispatch = function(eventName, componentName, ...args) {
  let parent = this.$parent;
  while (parent) {
    // 只有是特定組件,才會觸發事件。而不會一直往上,一直觸發
    const isSpecialComponent = parent.$options.name === componentName;
    if (isSpecialComponent) {
      // 觸發了,就終止循環
      parent.$emit(eventName, ...args);
      return;
    }
    parent = parent.$parent;
  }
};
複製代碼

這樣在el-input裏想要觸發el-form裏的方法,this.$dispatch('changeSort','el-form',{isAsc:true})

$broadcast

同理,假設el-form想要執行el-input上的方法,就會這樣this.$children[0].$children[0].methodXx(),更多層級可能更復雜,於是$broadcast就誕生了,使用的時候this.$broadcast('changeValue','el-input','hello')

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

// 向下通知某個組件,觸發事件
Vue.prototype.$broadcast = function(eventName, componentName, ...args) {
  // 這裏children是所有子組件,是子組件不是後代組件哈
  let children = this.$children;
  broadcast(children);
  // 這裏注意,抽離新的方法遞歸,而不是遞歸$broadcast
  function broadcast(children) {
    for (let i = 0; i < children.length; i++) {
      let child = children[i];
      const isSpecialComponent = child.$options.name === componentName;
      if (isSpecialComponent) {
        // 觸發了,就終止循環
        child.$emit(eventName, ...args);
        return;
      }
      // 沒觸發的話,就看下有沒有子組件,接着遞歸
      child.$children.length &&
        child.$broadcast(eventName, componentName, ...args);
    }
  }
};
複製代碼