vue 的組件化應該是其最核心的思想了,不管是一個大的頁面仍是一個小的按鈕,均可以被稱之爲組件。基於 Vue 的開發,就是在寫一個個組件,不管是基礎組件仍是業務組件,最後都是要拼裝在一塊兒。按照組件的層級關係,能夠把組件之間的關係概括爲:父子關係、隔代關係、兄弟關係,無關聯關係。vue
$ref
、$parent
、$children
基於當前上下文的,能夠經過 $ref
、$parent
、$children
訪問組件實例,能夠直接調用組件的方法或訪問數據。其中 $parent
能夠訪問當前組件的父組件,$children
能夠訪問當前組件的全部子組件。雖然 $parent
和 $children
均可以獲取組件實例,可是它們沒法在跨級或兄弟間通訊,這是它們的缺點。ajax
provide
、inject
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 來完成,而沒有引入第三方庫 vuex
。app
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>
dispatch
、 broadcast
這兩個 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,自行實現的方法有如下不一樣:
在 vue 中組件的通訊還有其餘的手法,例如:
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>
$attrs
、$listeners
Vue 2.4 新增的 API。$attrs
包含了父做用域中不做爲 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。$listeners
包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。
不一樣的通訊方法適用於不一樣的場景,既能夠經過 Vue 內置的 API 來通訊,也能夠經過自定義事件的方式實現通訊方法,當應用足夠複雜狀況下,就可使用 Vuex 進行數據管理。