组件间多种通信方式
- 组件间通信1: props
- 组件间通信2: vue自定义事件
- 组件间通信3: 事件总线
- 组件间通信4: v-model
- 组件间通信5: .sync 属性修饰符
- 组件间通信6: a t t r s 与 attrs与 attrs与listeners
- 组件间通信7: c h i l d r e n 与 children与 children与parent
- 组件间通信8: provide与inject
- 组件间通信9: vuex
- 组件间通信10: 作用域插槽slot-scope
- 组件间通信11:PubSub 项目中不会使用
1. props(父子)
- 用来实现父子之间相互通信的最基本方式, 也是用得最多的方式
- 父 ==> 子, 传递的是非函数类型的属性
- 子 ==> 父, 传递的是函数类型的属性
- 问题: 其它关系的组件使用props就会比较麻烦
演示
<Child :count="count" :add="add" />
export default {
name: "Child",
// 不声明props接受属性,属性就在$attrs
props: ["count"],
props: {
count: Number,
},
// props数据是只读不能修改
props: {
count: {
type: Number,
required: true,
validator(value) {
return value >= 0 && value <= 10;
},
// default: 0
},
add: {
type: Function,
required: true,
},
},
inheritAttrs: false, // 是否显示在元素标签上
};
2. vue自定义事件(一般给组件绑定)
<button @click="handleClick">按钮</button>
<MyButton @click="handleClick($event)" @xxx="xxx($event)" />
1) 原生DOM事件
- 绑定原生DOM事件监听的2种情况
- 在html标签上绑定DOM事件名的监听
- 在组件标签上绑定DOM事件名的监听, 事件绑定在组件的根标签上
- 当用户操作对应的界面时, 浏览器就会自动创建并封闭包含相关数据的事件对象, 分发对应的事件, 从而触发事件监听回调函数调用
- 事件对象event, 本质是 “事件数据对象”
- event对象内的数据属性: target / offsetX / offsetY / keyCode等
- $event就是浏览器创建的event对象, 默认传递给事件监听回调函数的就是它
2) vue自定义事件
- 绑定vue自定义事件监听
- 只能在组件标签上绑定
- 事件名是任意的, 可以与原生DOM事件名相同
- 只有当执行$emit(‘自定义事件名’, data)时分发自定义事件, 才会触发自定义事件监听函数调用
- $event: 就是分发自定义事件时指定的data数据
- $event可以是任意类型, 甚至可以没有
- 用来实现子向父组件通信, 功能相当于函数类型的props
事件:
-
原生DOM元素绑定事件
事件名是原生DOM事件事件名,绑定就是原生DOM事件
事件名不是原生DOM事件事件名,绑定就是自定义事件 -
组件绑定事件
所有事件都是自定义事件一般只会给组件绑定自定义事件,给普通元素绑定没有意义 绑定自定义事件目的: 给子组件传递一个函数,子组件触发这个函数从而修改父组件数据(子-->父) $event 代表事件触发时传递的第一个参数 事件修饰符 .prevent .stop .native 给组件绑定事件时使用的,作用:给组件的第一个元素绑定事件 按键修饰符 @keyup.enter @keyup.13 触发自定义事件方式: 1. this.$emit('eventName', xxx) 2. this.$listeners.eventName(xxx)
3) 应用场景:
在父组件下面有两个子组件,需要切换显示,切换显示是在子组件完成的 父组件定义切换显示的方法,通过自定义事件绑定给子组件,子组件通过触发自定义事件来切换显示 表格修改功能时,跳转到了新页面进行修改,点击取消要回来展示另外一个组件 <el-button @click="xxx"></el-button> 使用某个el组件时,他没有click功能,但是我们需要 @click.native
<MyButton @click.native="handleClick" />
// 子
<div>
<button>按钮</button>
</div>
3. 事件总线(任意组件)
全局事件总线:
1. 使用场景:任意组件
2. 原理:
基于自定义事件
将能定义事件对象(Vue的实例)绑定在Vue的原型对象上
在main.js的new Vue beforeCreate生命周期绑定
在这里绑定所有组件都可以使用
这样所有组件实例对象都能通过原型方式继承到事件对象,从而去绑定或触发事件
-
理解:
- Vue原型对象上有3个事件处理的方法: $on() / $emit() / $off()
- 组件对象的原型对象的原型对象是Vue的原型对象: 组件对象可以直接访问Vue原型对象上的方法
-
实现任意组件间通信
-
编码实现:
- 将入口js中的vm作为全局事件总线对象:
beforeCreate() {
Vue.prototype.$bus = this
}
-
分发事件/传递数据的组件: this. b u s . bus. bus.emit(‘eventName’, data)
-
处理事件/接收数据的组件: this. b u s . bus. bus.on(‘eventName’, (data) => {})
4. v-model(表单项)
给组件绑定了一个value属性,和input事件
等于是给组件绑定了一个v-bind和自定义事件 props和自定义事件的组合
<template>
<div>
<!-- 给组件绑定了一个value属性,和input事件 -->
<MyInput v-model="msg" />
</div>
</template>
- 子组件
<template>
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
/>
// 一样效果,带解构 但是不会这么去写,这样子就不用接收props
<input
type="text"
v-bind="$attrs"
@input="$emit('input', $event.target.value)"
/>
</template>
<script>
/*
应用场景:表单项
*/
export default {
name: "MyInput",
// props: ["value"],
mounted() {
console.log(this);
},
};
</script>
1) 原生input上的本质:
动态的value属性与原生input事件监听
<input type="text" :value="name2" @input="name2=$event.target.value">
2) 组件标签上的本质:
动态的value属性与自定义input事件监听
// 父组件:
<CustomInput :value="name4" @input="name4=$event"/>
// 子组件
props: ['value']
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
3) 利用v-model能做什么?
- v-model不仅能实现原生标签上的双向数据绑定, 也能实现父子组件间数据双向通信(同步)
- 应用
- 一般用于封装带表单项的复用性组件
- elment-ui中: Input/CheckBox/Radio/Select等表单项组件都封装了v-model
5: sync 属性修饰符
1) 理解本质:
绑定一个自定义事件监听, 用来接收子组件分发事件携带的最新数据来更新父组件的数据
<child :money.sync="total"/>
<Child :money="total" @update:money="total=$event"/>
2) 利用sync能做什么呢?
- 在原有父向子的基础上加上子向父通信
- 应用
- 常用于封装可复用组件(需要更新父组件数据)
- v-model一般用于带表单项的组件
- sync一般用于不带表单项标签的组件
- element-ui中: Dialog就利用sync来实现组件的隐藏
- 常用于封装可复用组件(需要更新父组件数据)
3)示例
<template>
<div>
<h1><router-link to="/">回到首页</router-link></h1>
<Child :count.sync="count"></Child>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Sync",
data() {
return {
count: 1,
};
},
components: {
Child,
},
};
</script>
<template>
<div>
<p>{{ count }}</p>
<button @click="$emit('update:count', count + 1)">点击数字加加</button>
</div>
</template>
<script>
export default {
name: "Child",
props: ["count"],
mounted() {
console.log(this);
},
};
</script>
6: $attrs
与$listeners
1) 理解:
-
$attrs: 排除props声明, class, style的所有组件标签属性组成的对象
-
$listeners: 级组件标签绑定的所有自定义事件监听的对象
-
v-bind: 的特别使用
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
-
v-on: 的特别使用:
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
一般: v-bind与
$attrs
配合使用, v-on与$listeners
配合使用
2) 使用它们能做什么呢?
- 在封装可复用组件时: HintButton
- 从父组件中接收不定数量/名称的属性或事件监听
- 在组件内部, 传递给它的子组件
- element-ui中: Input就使用了v-bind与$attrs来接收不定的属性传递给input
3) 扩展双击监听:
-
@dblclick="add2"
绑定是自定义事件监听, 而el-button内部并没处理(没有绑定对应的原生监听, 没有分发自定义事件)
双击时, 不会有响应 -
@dblclick.native="add2"
绑定的是原生的DOM事件监听, 最终是给组件的根标签a绑定的原生监听
当双击a内部的button能响应, 因为事件有冒泡
4) 示例
- 父组件的这三种写法效果都是相同的
<template>
<!-- <Child
v-bind="(person = person)"
@click="handleClick1"
@aaa="handleClick2"
@bbb="handleClick3"
></Child> -->
<!-- <Child
v-bind="person"
@click="handleClick1"
@aaa="handleClick2"
@bbb="handleClick3"
></Child> -->
<Child
:name="person.name"
:age="person.age"
:hobby="person.hobby"
@click="handleClick1"
@aaa="handleClick2"
@bbb="handleClick3"
></Child>
</template>
<script>
import Child from "./Child";
export default {
name: "AttrsAndListeners",
data() {
return {
person: {
name: "小明",
age: 18,
hobby: ["抽烟", "喝酒", "烫头"],
},
};
},
methods: {
handleClick1() {
console.log(123);
},
handleClick2() {
console.log(123);
},
handleClick3() {
console.log(123);
},
},
components: {
Child,
},
};
</script>
<template>
<div>
child
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
</template>
<script>
import C from "./C";
export default {
name: "Child",
components: {
C,
},
mounted() {
console.log(this);
},
};
</script>
<template>
<div>ccc</div>
</template>
<script>
export default {
name: "C",
mounted() {
console.log(this);
},
};
</script>
7: $children
与$parent
1) 理解:
- $children: 所有直接子组件对象的数组, 利用它可以更新多个子组件的数据
- $parent: 父组件对象, 从而可以更新父组件的数据
- $refs: 包含所有有ref属性的标签对象或组件对象的容器对象
2) 利用它们能做什么?
- 能方便的得到子组件/后代组件/父组件/祖辈组件对象, 从而更新其data或调用其方法
- 官方建议不要大量使用, 优先使用props和event
- 在一些UI组件库定义高复用组件时会使用 c h i l d r e n 和 children和 children和parent, 如Carousel组件
3) 扩展: mixin
- 多个组件有部分相同的js代码如何复用 ?
- 答: 利用vue的mixin技术实现
- 本质: 实现Vue组件的JS代码复用, 简化编码的一种技术
4) 练习mixin
- mixin.js
<template>
<div>
mixin
<p>{{ count }}</p>
<button @click="handleClick">Mixin</button>
</div>
</template>
<script>
import mixi from "./mixin";
export default {
name: "Mixin",
mixins: [mixi],
mounted() {
console.log(this);
},
};
</script>
- mixin.js
export default {
data() {
return {
count: 0
};
},
methods: {
handleClick() {
this.count++;
}
},
created() {
console.log("mixi created");
},
mounted() {
console.log("mixi mounted");
}
};
4)演示
<template>
<div>
<A></A>
<B></B>
</div>
</template>
<script>
import A from "./A";
import B from "./B";
export default {
name: "ChildrenAndParent",
data() {
return {
title: "Father",
};
},
components: {
A,
B,
},
mounted() {
console.log(this);
},
};
</script>
<template>
<div>aaa</div>
</template>
<script>
export default {
name: "A",
data() {
return {
title: "AAA",
};
},
mounted() {
console.log(this.$parent.title);
},
};
</script>
8: provide与inject
1) 理解
用来实现祖孙组件直接通信
在祖组件中通过provide配置向后代组件提供数据
在后代组件中通过inject配置来读取数据
2) 注意:
不太建议在应用开发中使用, 一般用来封装vue插件
provide提供的数据本身不是响应式的 ==> 父组件更新了数据, 后代组件不会变化
provide提供的数据对象内部是响应式的 ==> 父组件更新了数据, 后代组件也会变化
3) 应用:
element-ui中的Form组件中使用了provide和inject
9: vuex
-
实现任意组件间通信
-
Vuex 是一个专为 Vue 应用程序设计的管理多组件共享状态数据的 Vue 插件
任意组件都可以读取到Vuex中store的state对象中的数据任意组件都可以通过dispatch()或commit()来触发store去更新state中的数据
一旦state中的数据发生变化, 依赖于这些数据的组件就会自动更新
详情见vue分类之vuex
10.slot-scope
1) 什么情况下使用作用域插槽?
-
父组件需要向子组件传递标签结构内容
-
但决定父组件传递怎样标签结构的数据在子组件中
2) 编码
<!: 子组件: >
<slot :row="item" :$index="index"></slot>
<!: 父组件: >
<template slot-scope="{row, $index}">
<span>{{$index+1}}</span>
<span :style="{color: $index%2===1 ? 'blue' : 'green'}" >{{row.text}}</span>
</template>
3) 应用
- 对于封装列表之类的组件特别需要
- element-ui中: Table组件中就用到了slot-scope
插槽(父给子传递带数据标签)
- 使用场景:父给子传递带数据的标签
默认插槽
组件写成双标签,里面放入标签数据,那么这个标签数据就会以插槽的方式传递给子组件
// 父组件给子组件传递带数据的标签
<AChild>
<p>hello vue000</p>
<p>hello vue11</p>
<p>{{msg}}</p>
</AChild>
// 子组件使用
// 使用父组件以插槽方式传递的标签数据
<slot></slot>
具名、命名插槽
具名、命名插槽: 给每一个插槽取一个名字
// 具名、命名插槽: 给每一个插槽取一个名字
<BChild>
// 旧语法:slot="名称"
<template slot="header">
<header>头部...{{ title }}</header>
</template>
// 新语法:v-slot:名称
<template v-slot:main>
<main>内容区...</main>
</template>
// 新语法可以简写:#名称
<template #footer>
<footer>底部...</footer>
</template>
</BChild>
- 子组件使用
<template>
<div>
// 显示头部
// 通过name属性来决定使用哪个具名插槽
<slot name="header"></slot>
<p>---------</p>
// 显示内容区
<slot name="main"></slot>
<p>---------</p>
// 显示底部
<slot name="footer"></slot>
</div>
</template>
作用域插槽
- 父组件
<CChild>
// 父组件插槽可以接受子组件通过slot传递的props数据
<template #list="slotProps">
<template v-slot:list="slotProps">
// { person } -> 就是对数据进行解构赋值
<template #list="{ person }">
<template #list="{ person: { name, age } }">
<ul>
<li>姓名:{{ name }}</li>
<li>年龄:{{ age }}</li>
</ul>
</template>
</CChild>
- 子组件
<template>
<div>
// 以标签属性(props)方式传递person数据
<slot name="list" :person="person"></slot>
</div>
</template>
<script>
export default {
name: "CChild",
data() {
return {
person: {
name: "jack",
age: 18,
},
};
},
};
</script>
11.PubSub
项目中很少使用,详情见react分类中