vue組件通訊的幾種方式

寫在前面

vue 的組件化應該是其最核心的思想了,不管是一個大的頁面仍是一個小的按鈕,均可以被稱之爲組件。基於 Vue 的開發,就是在寫一個個組件,不管是基礎組件仍是業務組件,最後都是要拼裝在一塊兒。按照組件的層級關係,能夠把組件之間的關係概括爲:父子關係、隔代關係、兄弟關係,無關聯關係。vue

$ref$parent$children

基於當前上下文的,能夠經過 $ref$parent$children 訪問組件實例,能夠直接調用組件的方法或訪問數據。其中 $parent 能夠訪問當前組件的父組件,$children 能夠訪問當前組件的全部子組件。雖然 $parent$children 均可以獲取組件實例,可是它們沒法在跨級或兄弟間通訊,這是它們的缺點。ajax

provideinject

provide / inject 是 Vue 在 2.2.0 版本後新增的 API。vuex

這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。

也就是在父組件中提供一個值,而且在須要使用的子孫組件中注入改值,即:數組

// Parent.vue
export default {
    provide() {
        return {
            name: 'Stone'
        }
    }
}
// Child.vue
export default {
   inject: ['name'],
   mounted() {
       console.log(this.name)
   }
}

不單單是 Child.vue ,只要是 Parent.vue 的子組件,不管隔多少代,均可以經過這個的方式注入。 這種多級組件透傳的方式能夠保證單向數據流的清晰性,例如像用戶信息這樣的全局信息,就能夠藉助這兩個 API 來完成,而沒有引入第三方庫 vuexapp

替代 Vuex

vuex 是把數據集中管理,而後哪裏須要就在哪裏獲取,按照這個思路,咱們能夠在根組件 App.vue 中注入全局信息,而且在頁面的任何地方使用。ide

// App.vue
<template>
  <div>
    <router-view></router-view>
  </div>
</template>
export default {
    provide() {
        return {
            userInfo: this.user
        }
    },
    data() {
        return {
            user: {}
        }
    },
    methods: {
      getUserInfo () {
        $.ajax('/user/info', (data) => {
          this.user = data
        })
      }
    }
}

把整個 App.vue 的實例 this 對外提供, 這樣其餘頁面就能夠經過 this.userInfo 來獲取用戶信息。組件化

<template>
  <div>
    {{ userInfo }}
  </div>
</template>
<script>
  export default {
    inject: ['userInfo']
  }
</script>

$attrs$listeners

這兩個 API 是 Vue 2.4.0 新增的。 $attrs ,繼承全部的父組件屬性; $listeners ,它是一個對象,裏面包含了做用在這個組件上的全部監聽器。

主要用途就是用在父組件傳遞數據給子組件或者孫組件。this

<!-- Parent.vue -->
<template>
  <div id="app">
    <child1 :p-child1="child1"
            :p-child2="child2"
            :p-child3="child3"
            v-on:test1="onTest1"
            v-on:test2="onTest2">
    </child1>
  </div>
</template>
<script>
import Child1 from "./Child1.vue";
export default {
  data() {
    return {
        child1: 'child1',
        child2: 'child2',
        child3: 'child3'
    };
  },
  components: { Child1 },
  methods: {
    onTest1() {
      console.log("test1 running...");
    },
    onTest2() {
      console.log("test2 running");
    }
  }
};
</script>
<!-- Child1.vue -->
<template>
    <div class="child-1">
        <p>in child1:</p>
        <p>props: {{pChild1}}</p>
        <p>$attrs: {{$attrs}}</p>
        <hr>
        <!-- Child2 組件中能直接觸發 test 的緣由在於 Child1 組件調用 Child2 組件時 使用 v-on 綁定了 $listeners 屬性 -->
        <!-- 經過 v-bind 綁定 $attrs 屬性,Child2 組件能夠直接獲取到 Parent 組件中傳遞下來的 props(除 了Child1 組件中 props 聲明的) -->
        <child2 v-bind="$attrs" v-on="$listeners"></child2>
    </div>
</template>
<script>
import Child2 from './Child2.vue';
export default {
    props: ['pChild1'],
    data() {
        return {};
    },
    inheritAttrs: false,
    components: { Child2 },
    mounted() {
        this.$emit('test1');
    }
};
</script>
<!-- Child2.vue -->
<template>
    <div class="child-2">
        <p>in child2:</p>
        <p>props: {{pChild2}}</p>
        <p>$attrs: {{$attrs}}</p>
        <hr>
    </div>
</template>
<script>
export default {
    props: ['pChild2'],
    data() {
        return {};
    },
    inheritAttrs: false,
    mounted() {
        this.$emit('test2');
    }
};
</script>

dispatchbroadcast

這兩個 API 是 Vue 1.0 版本的,$dispatch 用於向上級派發事件,$broadcast 用於向下級廣播事件的,它們在 2.0 版本中已經被廢棄了。spa

由於基於組件樹結構的事件流方式有時讓人難以理解,而且在組件結構擴展的過程當中會變得愈來愈脆弱。

不過咱們能夠自行實現 dispatch 和 broadcast 方法,用於組件的跨層級通訊。它們實現的關鍵在於如何正確找到所要通訊的組件,也就是經過匹配組件的 name 選項向下或向上查找組件。code

這兩個方法都須要傳遞 3 個參數,第一個參數是要通訊組件的 name 選項值,第二個是自定義事件名稱,第三個是要給通訊組件傳遞的值。在 dispatch 裏,經過 while 循環不斷向上查找父組件,直到查找到 componentName 與某個父級組件的 name 選項匹配時結束,此時改父組件就是要通訊的組件。 broadcast 方法與 dispatch 相似,是經過 forEach 循環向下查找子組件。 最後封裝一個 mixins 來便於複用。

// emitter.js 
function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    const name = child.$options.name;
    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) {
          name = parent.$options.name;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

經過 mixins 混入組件,實現組件間的通訊。

<!-- Parent.vue -->
<template>
  <button @click="handleClick">
    觸發事件
    <Child></Child>
  </button>
</template>
<script>
import Emitter from "../assets/js/emitter.js";
import Child from "./Child.vue";
export default {
  name: "Parent",
  mixins: [Emitter],
  created() {
    this.$on("on-message", message => {
      console.log(message);
    });
  },
  components: {
    Child
  },
  methods: {
    handleClick() {
      this.broadcast("Child", "on-message", "Hello, I am Parent Component");
    }
  }
};
</script>
<!-- Child.vue -->
<template>
  <div></div>
</template>
<script>
import Emitter from "../assets/js/emitter.js";
export default {
  name: "Child",
  mixins: [Emitter],
  mounted() {
    this.$on("on-message", message => {
      console.log(message);
      this.dispatch("Parent", "on-message", "Copy that, I am Child Component");
    });
  }
};
</script>

相比 Vue 1.0 版本內置的兩個 API,自行實現的方法有如下不一樣:

  • 須要額外傳入組件的 name 做爲第一個參數;
  • 匹配到組件就會中止循環,不會冒泡;
  • 傳遞的值只能是一個參數,若是須要傳遞多個參數,只能經過對象或數組的形式;

其餘方法

在 vue 中組件的通訊還有其餘的手法,例如:

  1. props$emit
<!-- Parent.vue -->
<template>
  <Child :info="info"
         @update="onUpdateName"></Child>
</template>
<script>
import Child from "./Child.vue";
export default {
  data() {
    return {
      info: {
        name: "stone"
      }
    };
  },
  components: {
    Child
  },
  methods: {
    onUpdateName(name) {
      this.info.name = name;
    }
  }
};
</script>
<!-- Child.vue -->
<template>
  <div>
    <div>{{info.name}}</div>
    <button @click="handleUpdate">update</button>
  </div>
</template>
<script>
export default {
  props: {
    info: {
      type: Object,
      default: {}
    }
  },
  methods: {
    handleUpdate() {
      this.$emit("update", "new-name");
    }
  }
};
</script>
  1. 父組件將本身的方法傳遞給子組件,子組件調用方法傳數據給父組件
  2. 使用event bus事件總線
  3. $attrs$listeners
Vue 2.4 新增的 API。 $attrs 包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。 $listeners 包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。
  1. Vuex 集中式狀態管理

寫在最後

不一樣的通訊方法適用於不一樣的場景,既能夠經過 Vue 內置的 API 來通訊,也能夠經過自定義事件的方式實現通訊方法,當應用足夠複雜狀況下,就可使用 Vuex 進行數據管理。

相關文章
相關標籤/搜索