Vue組件之間的通訊是咱們在項目中經常碰到的,而選擇合適的通訊方式尤其重要,這裏總結下做者在實際項目中所運用到的通訊方案,若有遺漏,請你們見諒。文章代碼具體見DEMO;文章首發於imondo.cnvue
Vue中常見的是父與子組件間的通訊,所要用到的關鍵字段是props
和$emit
。webpack
props
接受父組件傳給子組件信息的字段,它的類型:Array<string> | Object
;詳細解釋能夠參考文檔git
$emit
由子組件觸發事件向上傳播給父級消息。github
示例:web
// 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>
複製代碼
效果以下:vue-cli
有時候咱們可能會碰到組件間的無限嵌套,這時咱們使用props
時沒法向下無限極傳遞數據的,咱們能夠用到provide/inject
;provide
能夠向其子孫組件傳遞數據,而不關子孫組件的層級有多深,使用inject
均可以拿到數據。詳細解釋能夠參考文檔api
示例:bash
// 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>
複製代碼
效果以下:app
provide
和inject
綁定並非可響應的。咱們能夠經過傳遞祖父級的實例this
或着使用observable
來使傳遞的數據是響應的。ide
// 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>
複製代碼
效果以下:
使用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 };
複製代碼
項目中組件的通訊方式大概經常使用的是上面幾種方案,咱們能夠經過不一樣的方式來實現組件通訊,可是選擇合適組件通訊方式可使咱們事半功倍。寫的不當之處,望指正~
其餘總結文章: