Vue組件之間的通訊是咱們在項目中經常碰到的,而選擇合適的通訊方式尤其重要,這裏總結下做者在實際項目中所運用到的通訊方案,若有遺漏,請你們見諒。文章代碼具體見DEMO;文章首發於imondo.cnvue
Vue中常見的是父與子組件間的通訊,所要用到的關鍵字段是props
和$emit
。git
props
接受父組件傳給子組件信息的字段,它的類型:Array<string> | Object
;詳細解釋能夠參考文檔github
$emit
由子組件觸發事件向上傳播給父級消息。vue-cli
示例:api
// Parent <template> <div class="parent"> 我是父組件 <p>來自子級的回答:{{ childMsg }}</p> <Child :msg="msg" @click="handleClick"/> </div> </template> <script> import Child from "./Child"; export default { name: "Parent", components: { Child }, data() { return { msg: "叫你吃飯了", childMsg: '' }; }, methods: { // 接收來自子級的事件消息 handleClick(val) { this.childMsg = val; } } }; </script> // Child <template> <div class="child"> <p>我是子組件</p> <p>父級來的信息: {{ msg }}</p> <button @click="handleClick">回答父級</button> </div> </template> <script> export default { name: "Child", // 接收父級傳來的信息 props: { msg: String }, methods: { // 向父級傳播事件消息 handleClick() { this.$emit('click', '我知道了'); } }, }; </script>
效果以下:app
有時候咱們可能會碰到組件間的無限嵌套,這是咱們使用props
時沒法向下無限極傳遞數據的,這是咱們能夠用到provide/inject
;provide
能夠向其子孫組件傳遞數據,而不關子孫組件的層級有多深,使用inject
均可以拿到數據。詳細解釋能夠參考文檔ide
示例:函數
// Grand <template> <div class="grand"> <p>我是祖父</p> <Parent /> </div> </template> <script> export default { name: "Grand", provide: { grandMsg: '都來吃飯' }, components: { Parent } }; </script> // Parent <template> <div class="parent"> 我是父組件 <p>祖父的信息:{{ grandMsg }}</p> <Child /> </div> </template> <script> import Child from "./Child"; export default { name: "Parent", components: { Child }, inject: { grandMsg: { default: '' } } }; // Child <template> <div class="child"> <p>我是子組件</p> <p>爺爺的信息: {{ grandMsg }}</p> </div> </template> <script> export default { name: "Child", inject: { grandMsg: { default: '' } } }; </script>
效果以下:ui
provide
和inject
綁定並非可響應的。咱們能夠經過傳遞祖父級的實例this
或着使用observable
來使傳遞的數據是響應的。
// Grand <template> <div class="grand"> <p>我是祖父</p> <input type="text" v-model="msg" placeholder="輸入祖父的消息"/> <Parent /> </div> </template> <script> import Parent from "./Parent"; export default { name: "Grand", provide() { return { // 利用函數 provide 返回對象 grandVm: this // 傳遞實例 }; }, ... data() { return { msg: "" }; } }; </script> // Child <template> <div class="child"> <p>我是子組件</p> <p>爺爺的實例信息: {{ grandVmMsg }}</p> </div> </template> <script> export default { name: "Child", inject: { grandVm: { default: () => { ""; } } }, computed: { grandVmMsg() { return this.grandVm.msg; } } }; </script>
效果以下:this
使用observable
讓一個對象可響應。Vue 內部會用它來處理 data 函數返回的對象。
示例:
// Grand provide() { this.read = Vue.observable({ msg: '' }) return { read: this.read }; }
效果以下:
同級別組件相互間的通訊,咱們可使用EventBus
或着Vuex
。
簡單的EventBus
示例:
// Bus.js import Vue from "vue"; export default new Vue(); // Child <div class="child"> <p>我是子組件一</p> <button @click="handleClick">組件一事件</button> </div> <script> import Bus from "./Bus"; export default { name: "Child", methods: { handleClick() { Bus.$emit("click", "嘿,老鐵"); } } }; </script> // ChildOne <div class="child"> <p>我是子組件二</p> <p>兄弟叫我:{{ msg }}</p> </div> <script> import Bus from "./Bus"; export default { name: "ChildOne", data() { return { msg: "" }; }, mounted() { Bus.$on("click", msg => { this.msg = msg; }); } }; </script>
效果以下:
v-model
與sync
v-model
是咱們用ElementUI
常見的表單綁定值方式;能夠直接修改子組件修改父組件傳入的值,簡化了咱們組件通訊的邏輯。
示例:
// ModelCom <div class="child"> <input type="text" @input="handleInput"> </div> <script> export default { name: "ModelSync", methods: { // 經過綁定表單input中的input事件,向上觸發input事件來修改值 handleInput(e) { const value = e.target.value; this.$emit('input', value); } } }; </script> // Home <ModelSync v-model="msg"/>
效果以下:
sync
修飾符也能夠是咱們的prop
進行雙向綁定。
它須要咱們在子組件內觸發this.$emit('update:prop', val)
事件
// ModelCom <input type="text" @input="handleChange"> ... props: ['value'], methods: { handleChange(e) { const value = e.target.value; // 觸發更新 this.$emit('update:value', value); } } // Home <ModelSync :value.sync="syncMsg"/>
效果以下:
$children
與$parent
咱們能夠在組件中經過當前的實例對象訪問到組件的$children
和$parent
來找到各自組件的父級組件或子級組件實例。
示例:
// Child <div class="child"> <p>我是子組件</p> <p>來自父組件的msg: {{ msg }}</p> </div> ... <script> export default { name: "ChildParent", data() { return { value: '' } }, computed: { msg() { return this.$parent.value; } }, created() { console.log(this.$parent); } } // Parent <input v-model="value" />
經過在父組件中輸入值能夠看到子組件數據也同時更新了
$attrs
與$listeners
$attrs
能夠經過 v-bind="$attrs"
將組件上的特新都(class 和 style 除外)傳入內部組件;傳入的值與inheritAttrs
的設置有關,一般封裝高級組件。
當咱們inheritAttrs
設置 true
;組件渲染DOM時寫在組件的特性會渲染上去;
$listeners
包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件。
具體詳細可見文檔
示例:
// Attr <div class="child"> <p>Attr</p> <p>這是$attrs:{{ placeholder }}</p> <p>這是$listeners:{{ test }}</p> <button @click="$listeners.click">監聽了$listeners</button> </div> ... <script> export default { name: "AttrListen", inheritAttrs: true, props: { test: { type: String, default: '' } }, data() { return { placeholder: this.$attrs.placeholder } } }; </script> // Home <AttrListen placeholder="這是個attr" :test="value" v-bind="$attrs" v-on="$listeners" @click="handleListen"/>
效果以下:
經過封裝函數來向上或向下派發事件
參考見Vue.js組件精講
// 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); } } };
經過封裝函數來查找指定任意組件
參考見Vue.js組件精講
// 由一個組件,向上找到最近的指定組件 function findComponentUpward (context, componentName) { let parent = context.$parent; let name = parent.$options.name; while (parent && (!name || [componentName].indexOf(name) < 0)) { parent = parent.$parent; if (parent) name = parent.$options.name; } return parent; } export { findComponentUpward }; // 由一個組件,向上找到全部的指定組件 function findComponentsUpward (context, componentName) { let parents = []; const parent = context.$parent; if (parent) { if (parent.$options.name === componentName) parents.push(parent); return parents.concat(findComponentsUpward(parent, componentName)); } else { return []; } } export { findComponentsUpward }; // 由一個組件,向下找到全部指定的組件 function findComponentsDownward (context, componentName) { return context.$children.reduce((components, child) => { if (child.$options.name === componentName) components.push(child); const foundChilds = findComponentsDownward(child, componentName); return components.concat(foundChilds); }, []); } export { findComponentsDownward }; // 由一個組件,找到指定組件的兄弟組件 function findBrothersComponents (context, componentName, exceptMe = true) { let res = context.$parent.$children.filter(item => { return item.$options.name === componentName; }); let index = res.findIndex(item => item._uid === context._uid); if (exceptMe) res.splice(index, 1); return res; } export { findBrothersComponents };
項目中組件的通訊方式大概經常使用的是上面幾種方案,咱們能夠經過不一樣的方式來實現組件通訊,可是選擇合適組件通訊方式可使咱們事半功倍。寫的不當之處,望指正~