【Vue项目总结】组件通信处理方案
Vue组件之间的通信是我们在项目中常常碰到的,而选择合适的通信方式尤为重要,这里总结下作者在实际项目中所运用到的通信方案,如有遗漏,请大家见谅。文章代码具体见DEMO。
父子组件
Vue中常见的是父与子组件间的通信,所要用到的关键字段是props
和$emit
。
props
接受父组件传给子组件信息的字段,它的类型:Array<string> | Object
;详细解释可以参考文档
$emit
由子组件触发事件向上传播给父级消息。
示例:
// 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>
效果如下:
祖孙组件
有时候我们可能会碰到组件间的无限嵌套,这时我们使用props
时无法向下无限极传递数据的,我们可以用到provide/inject
;provide
可以向其子孙组件传递数据,而不关子孙组件的层级有多深,使用inject
都可以拿到数据。详细解释可以参考文档
示例:
// 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>
效果如下:
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>
效果如下:
使用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 };
总结
项目中组件的通信方式大概常用的是上面几种方案,我们可以通过不同的方式来实现组件通信,但是选择合适组件通信方式可以使我们事半功倍。写的不当之处,望指正~