Vue——组件通信方式
组件间的通信一般有以下几种形式:
一、ref
ref: 给元素或组件注册引用信息
用ref访问组件实例:
//子组件component-a
export default{
data(){
return{
title: 'hello ref'
}
},
methods:{
sayHello(){
window.alert('world');
}
}
}
//父组件
<template>
<component-a ref = "comA"></component-a>
</template>
<script>
export default{
mounted(){
const comA = this.$refs.comA;
console.log(comA.title);
comA.sayHello();
}
}
</script>
二、$
parent/$
children
1、$
children 获取子组件数据和调用子组件方法
//父组件
<template>
<component-a></component-a>
</template>
<script>
export default{
methods:{
do() {
this.$children[0].sayHello() // 调用子组件方法 $children获取的是父组件的直接子组件数组,不获取孙组件
this.$children[0].title= 'hello' // 改变子组件数据
}
}
</script>
2、$
parent 获取父组件数据和调用父组件方法
//子组件component-a
export default{
data(){
return{
title: 'hello ref'
}
},
methods:{
sayHello(){
window.alert('world');
},
show(){
this.$parent.do() //获取直属父组件
}
}
}
三、props
1、prop属性
prop定义了组件中可配置的属性。props最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值。
//子组件<i-button>
<template>
<button :class="'i-button-size' + size" :disabled="disabled"></button>
</template>
<script>
export default{
props:{
size:{
default: 'default'
},
disabled:{
type: Boolean,
default: false
}
}
}
</script>
//父组件
<template>
<i-button size="large"></i-button>
<i-button disabled = true></i-button>
</template>
2、html特性参数
使用组件时,也可以传入一些标准的html特性,比如id、class:
<i-button id="btn" class = "btn-submit"> </i-button>
HTML特性在组件内的<button>
元素上会继承,并不需要在props中定义。
注意:
组件中定义的props都是单向数据流,只能通过父组件修改。
四、插槽slot
给子组件i-button添加一些文字内容:
//子组件<i-button>
<template>
<button :disabled="disabled">
<slot></slot>
</button>
</template>
//父组件
<template>
<i-button>
<strong>按钮</strong>
</i-button>
</template>
当需要多个插槽时,需要用到具名slot,比如再设置一个图标组件:
//子组件<i-button>
<template>
<button :disabled="disabled">
<slot name="icon"></slot>
<slot></slot>
</button>
</template>
//父组件
<template>
<i-button>
<i-icon slot="icon" type = "checkmark"></i-icon>
<strong>按钮</strong>
</i-button>
</template>
注意:
在slot中也可以定义一些默认的内容,当父组件没有写任何内容时就会出现:
<slot> 默认内容 </slot>
参考文章:
[1] VUE-插槽slot
五、自定义事件event
通过$emit
可以在子组件中自定义事件on-click
,在父组件中通过@on-click
来监听。
//子组件<i-button>
<template>
<button @click="handleClick"></button>
</template>
<script>
export default{
methods:{
handleClick(event){
this.$emit('on-click',event);
}
}
}
</script>
//父组件
<i-button @on-click="handleClick"></i-button>
六、provide/inject
1、基本用法
在父组件中通过provide将变量name提供给所有的子组件。
//父组件
export default{
provide:{
name: 'Aresn'
}
}
在子组件中通过inject注入name变量,便可以直接使用this.name访问这个变量了。
//子组件
export default{
inject: ['name'],
mounted(){
console.log(this.name);
}
}
注意:
provide和indect绑定不是可响应的,即父组件的name发生了变更,子组件中并不会变。
2、使用provide/inject代替Vuex
(1)基本用法
Vue通常会导入一个入口组件app.vue作为根组件。我们可以将app.vue用来存储所有需要的全局数据和状态,甚至是计算属性、方法等。因为项目中的所有组件的父组件都是app.vue,所以我们可以将app.vue实例,通过provide对外提供。
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
provide(){
return{
app: this
}
}
}
</script>
接下来,任何组件通过inject注入app.vue的app,都可以直接通过this.app.xxx来访问app.vue的data、computed、methods等内容。
注意:
app.vue是整个项目第一个被渲染的组件,而且只会渲染一次,利用这个特性可以做一次性全局的状态数据管理。
(2)进阶用法——引入混合mixins
当项目比较复杂时,app.vue中的代码就会变得结构复杂难以维护。这时可以使用Vue.js的混合mixins,将不同的逻辑分到不同的js文件中。
//user.js
export default {
data(){
return{
userInfo:null
}
},
methods:{
getUserInfo(){
this.userInfo = 'hello';
}
}
}
引入混合
//user.js
<script>
import mixins_user from './mixins/user.js'
export default {
mixins: [mixins_user],
data(){
return{
}
}
}
</script>
七、自定义dispatch和broadcast方法
Vue.js 2.x中已经废弃的方法dispatch和broadcast方法。
dispatch: 用于向上级派发事件,它的父级(一级或多级)都可以通过$on监听到 。
broadcast: 用于从上级向下级广播事件。
示例:dispatch
//子组件
<template>
<button @click="handleClick"></button>
</template>
<script>
export default{
methods:{
handleClick(event){
this.$dispatch('on-click','hello');
}
}
}
</script>
//父组件
export default{
mounted(){
this.$on('on-click',(text)=>{
console.log(text);
})
}
}
想要在Vue2.x 以上版本使用dispatch和broadcast方法需要自己实现:
//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);
}
}
};
使用:
//componentA
<template>
<button @click="handleClick"></button>
</template>
<script>
import Emitter from './mixins/emitter.js';
export default{
name: 'componentA',
mixins: [Emitter],
methods:{
handleClick(){
this.$broadcast('componentB','on-message','Hello ComB');
}
}
}
</script>
//componentB
export default{
name: 'componentB',
created(){
this.$on('on-message',this.showMessage);
}
methods:{
showMessage(text){
window.alert(text);
}
}
参数说明:
broadcast(componentName, eventName, params)
- componentName: 组件名
- eventName:事件名
- params:只能传一个参数,多参数可通过对象的形式传递。
来源:《Vue.js组件精讲》
八、找到任意组件实例
//assist.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 findComponentDownward (context, componentName) {
const childrens = context.$children;
let children = null;
if (childrens.length) {
for (const child of childrens) {
const name = child.$options.name;
if (name === componentName) {
children = child;
break;
} else {
children = findComponentDownward(child, componentName);
if (children) break;
}
}
}
return children;
}
export { findComponentDownward };
// 由一个组件,向下找到所有指定的组件
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 };
function typeOf(obj) {
const toString = Object.prototype.toString;
const map = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Undefined]': 'undefined',
'[object Null]' : 'null',
'[object Object]' : 'object'
};
return map[toString.call(obj)];
}
// deepCopy
function deepCopy(data) {
const t = typeOf(data);
let o;
if (t === 'array') {
o = [];
} else if ( t === 'object') {
o = {};
} else {
return data;
}
if (t === 'array') {
for (let i = 0; i < data.length; i++) {
o.push(deepCopy(data[i]));
}
} else if ( t === 'object') {
for (let i in data) {
o[i] = deepCopy(data[i]);
}
}
return o;
}
export {deepCopy};
使用:
<script>
import {findComponentUpward} from '../utils/assist.js';
export default{
name: 'comPonentB';
mounted(){
const comA = findComponentUpward(this,'componentA');
}
}
</script>
参数说明:
this: 当前组件的上下文实例
‘componentA’: 要找的组件实例名
来源:《Vue.js组件精讲》
九、VUEX
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
链接:Vuex文档