一,组件间高级通信,准备代码
准备工作
1、把定义好的communication组件注册到我们的前台项目当中
2、修改路由配置,路由组件全是函数模式,路由懒加载
3、注册element-ui的button组件
路由配置
{
path: "/communication",
component: () => import("@/pages/Communication/Communication"),
children: [
{
path: "event",
component: () => import("@/pages/Communication/EventTest/EventTest"),
meta: {
isHideFooter: true,
},
},
{
path: "model",
component: () => import("@/pages/Communication/ModelTest/ModelTest"),
meta: {
isHideFooter: true,
},
},
{
path: "sync",
component: () => import("@/pages/Communication/SyncTest/SyncTest"),
meta: {
isHideFooter: true,
},
},
{
path: "attrs-listeners",
component: () =>
import("@/pages/Communication/AttrsListenersTest/AttrsListenersTest"),
meta: {
isHideFooter: true,
},
},
{
path: "children-parent",
component: () =>
import("@/pages/Communication/ChildrenParentTest/ChildrenParentTest"),
meta: {
isHideFooter: true,
},
},
{
path: "scope-slot",
component: () =>
import("@/pages/Communication/ScopeSlotTest/ScopeSlotTest"),
meta: {
isHideFooter: true,
},
},
],
},
然后到该组件,http://localhost:8080/#/communication
Vue组件间通信深入
1. 组件间通信(传值)的多种方式
1. 组件间通信最基本方式: props
2. 组件间通信高级1: vue自定义事件与事件总线
3. 组件间通信高级2: v-model深入
4. 组件间通信高级3: 属性修饰符sync
5. 组件间通信高级4: $attrs与$listeners
6. 组件间通信高级5: $children与$parent
7. 组件间通信高级6: 作用域插槽slot-scope
8. 组件间通信高级7: vuex
2.1. 组件间通信最基本方式: props
props 子组件声明接收属性三种写法 【‘todos’】 { todos:Array} { todos:{type:Array,default:[]}}
父子之间
父可以给子传递 非函数和函数
传非函数数据 就是父给子
传函数数据 本质是父想要子的数据
特殊:
路由配置 props(三种) 路由组件之间没有标签,但是可以把参数通过路由映射为属性
1.1. 组件间通信高级1: vue自定义事件与事件总线
1.1.1. 什么条件下绑定的原生DOM事件监听?
1. 给html标签绑定dom事件监听:
<div @click="handleClick">
2. 给组件标签绑定dom事件监听(使用.native):
<MyCommponent @click.native="handleClick">
原生dom事件在html标签和组件标签上的区别 (Event1组件测试)
在html标签上添加就是原生的dom事件
在组件标签上添加就是自定义事件,想成为原生的事件得添加修饰符.native,就是把原生dom事件添加到组件根元素上
1.1.2. 什么条件下绑定的vue自定义事件监听?
1. 自定义事件名: <MyComponent @xxx="handleClick2">
2. 与dom事件名同名: <MyComponent @click="handleClick">
1.1.3. 如何触发/分发事件?
1. 触发原生DOM事件:
(1) 浏览器自动分发: 用户操作界面对应元素时, 浏览器自动分发/触发相应的事件
(2) 传递数据: 固定为包含所有相关数据的对象event
2. 触发vue自定义事件:
(1) 必须编码分发: vm.$emit(事件名, 数据)
(2) 传递数据: 由emit()事件名后面的参数指定, 当然也可以不指定
(3) 事件监听回调: 默认变量$event就是分发事件时指定的数据
编码测试
1. Event1.vue <template> <div style="background: #aaa"> <h2>Event1组件</h2> <span>其它内容</span> </div> </template> 2. Event2.vue <template> <div> <h2>Event2组件</h2> <button @click="$emit('click', 123)">分发自定义click事件</button><br> <button @click="$emit('xxx', 'atguigu')">分发自定义xxx事件</button><br> </div> </template> 3. EventTest.vue <template> <div> <h1>EventTest组件</h1> <button @click="test1">测试原生事件</button> <hr> <Event1 @click.native="test2($event)"/> <hr> <Event2 @click="test3" @xxx="test4($event)" /> </div> </template> <script type="text/ecmascript-6"> import Event1 from './Event1.vue' import Event2 from './Event2.vue' export default { methods: { test1 (event) { alert('test1() ' + event.target.innerHTML) }, test2 (event) { alert('test2() ' + event.target.innerHTML) }, test3 (data) { alert('click test3() ' + data ) }, test4 (data) { alert('xxx test4() ' + data ) } }, components: { Event1, Event2, } } </script>
1.1.4. 全局事件总线
1. 前置知识:
(1) Vue原型对象上有3个事件处理的方法: $on() / $emit() / $off()
(2) 组件对象的原型对象是vm对象: 组件对象可以直接访问Vue原型对象上的方法
(3) 实现任意组件间通信
2. 实现
(1) 将入口js中的vm作为全局事件总线对象:
beforeCreate() {
Vue.prototype.$bus = this
}
(2) 分发事件/传递数据的组件: this.$bus.$emit('eventName', data)
(3) 处理事件/接收数据的组件: this.$bus.$on('eventName', (data) => {})
1.1. 组件间通信高级2: v-model深入理解
1.1.1. v-model本质
1. 官方文档: 在组件中使用 v-model
2. 本质是动态value属性与input事件监听的语法糖
3. 在原生input标签上使用
<input v-model="searchText"> |
等价于
<input |
4. 在组件标签上使用
<custom-input v-model="searchText"> |
等价于
<custom-input |
1.1.2. 利用v-model能做什么呢?
1. v-model不仅能实现原生标签的双向数据绑定, 也能实现组件标签的双向数据绑定
2. 实现父子组件间数据双向同步
3. 一般用于封装带表单项的复用性组件
4. elment-ui中的Input/CheckBox/Radio/Select等表单项组件都封装了v-model
1、html input v-model的本质
:value = “data” //读取数据
@input = "data = $event.target.value" //写数据
2、组件标签上 v-model本质
:value = "data" 父组件传递属性给子组件,子组件需要接受
@input = "data = $event"
数据在父组件当中
子组件当中必须这样写
先接受props:['value']
子组件表单类元素
:value = "value"
@input = "@emit('input',$event.target.value)"
1.1.3. 编码测试
@input属于原生dom事件
ModelTest.vue父组件在子组件CustomInput.vue设置v-model,此时,子组件的name改变,导致父组件页的name跟着变化,那父组件的name改变,props传递的值给子组件也跟着变化
1. ModelTest.vue
<template> |
2. CustomInput.vue
<template> |
1.1.1. 利用sync能做什么呢?
- 1. 实现父子组件间数据双向同步
- 2. 常用于封装可复用组件
- 3. element-ui的Dialog就利用sync来实现组件的隐藏
实现父子组件双向数据同步问题 和 v-model 实现效果几乎一样 v-model一般用于带表单项的组件 sync属性修饰符一般用于不带表单项的组件 父组件给子组件属性传递数据后面添加.sync 子组件修改数据 需要分发事件@click = $emit("update:属性名",要更新的数据) 本质上还是自定义事件 <!-- <SpuForm v-show="isShowSpuForm" :visible="isShowSpuForm" @update:visible="isShowSpuForm=$event"></SpuForm> --> 父组件给子组件传递 isShowSpuForm数据, <SpuForm v-show="isShowSpuForm" :visible.sync="isShowSpuForm"></SpuForm> <el-button @click="$emit('update:visible',false)">返回</el-button>
1.1.2. 编码测试
需求: 假设这么一个场景:假设小明的父亲有1000块钱,小明一次花掉100元,就是点击一次花钱按钮父亲的钱减少100。
父组件SyncTest.vue,通过sync修饰符,将total传递给子组件child, 然后子组件数据变动,父组件的数据也跟着变动
- SyncTest.vue
<template> |
- Child.vue
<template> |
- Child2.vue
<template> |
1.1. 组件间通信高级4: $attrs与$listeners
$attrs和$linsteners(六) 本质就是父组件中给 子组件传递的所有属性组成的对象及自定义事件方法组成的对象 子组件 <HintButton type="danger" icon="el-icon-delete" title="删除" @heihei="test1" @click="test2"></HintButton> 子组件内部: <a href="javascript:;" :title="title"> <el-button v-bind="$attrs" v-on="$listeners"></el-button> </a> $attrs 如果不声明props 那么子组件当中是可以看到 如果声明了哪个属性,那么那个属性在$attrs当中看不到 它会排除 props声明接收的属性 以及class style 可以通过v-bind 一次性把父组件传递过来的属性添加给子组件 可以通过v-on 一次性把父组件传递过来的事件监听添加给子组件 对一个组件进行二次封装 8、 element-ui的button添加click事件会触发,添加dblclick就不会触发的问题 element-ui的button 子组件内部触发了这个单击事件 <el-button type="primary" @click="test1">测试</el-button> element-ui的button 子组件内部没有触发这个双击事件 <el-button type="danger" icon="el-icon-delete" @dbclick="test1">双击事件</el-button> 扩展双击点击触发element-ui button事件,使用原生。native
1.1.1. 理解
1. $attrs: 排除props声明, class, style的所有组件标签属性组成的对象
2. $listeners: 级组件标签绑定的所有自定义事件监听的对象
3. 一般: v-bind与$attrs配合使用, v-bind与$listeners配合使用
1.1.2. 利用它们能做什么?
- 1. 在封装可复用组件时
(1) 从父组件中接收不定数量/名称的属性或事件监听
(2) 在组件内部, 并传递给它的子组件
- 2. element-ui中: Input就使用了v-on与$attrs来接收不定的属性传递给input
1.1.3. 编码测试
需求: 对el-button进行二次封装, 实现使用按钮能有hover的文本提示
1. LinkButton.vue
<template> |
2. AttrsListenersTest.vue
<template> |
1.1. 组件间通信高级5: $children与$parent属性
1.1.1. 理解
1. $children: 所有直接子组件对象的数组(注意不确定顺序)
2. $parent: 父组件对象
3. $refs: 包含所有有ref属性的标签对象或组件对象的容器对象
$parent 和 $children以及$ref(七)
$children:所有子组件对象的数组
this.$children.forEach(item=>{
item.money -=money
})
$parent:代表父组件对象
this.$parent.money +=money
父组件当中可以通过$children找到所有的子组件去操作子组件的数据(当然可以找孙子组件)
子组件当中可以通过$parent找到父组件(当然可以继续找爷爷组件)操作父组件的数据
1.1.2. 利用它们能做什么?
1. 能方便的得到子组件/后代组件/父组件/祖辈组件对象, 从而更新其data或调用其方法
2. 官方建议不要大量使用, 优先使用props和event
3. 在一些UI组件库定义高复用组件时会使用$children和$parent, 如Carousel组件
1.1.1. 扩展: Vue组件Mixin技术
1. 官方文档: mixin
2. 什么时候使用: 当多个组件的JS配置部分有一些相同重复的代码时
3. 本质: 就是Vue的mixin技术是实现Vue组件的JS代码复用, 简化编码的一种技术
1.1.2. 编码测试
1. ChildrenParentTest.vue
<template> |
2. Son.vue
<template> |
3. Daughter.vue
<template> |
4. mixins.js
/* |
1.1. 组件间通信高级6: 作用域插槽slot-scope
1.1.1. 理解
1. 官方文档: 作用域插槽(scoped slots)
2. 什么情况下使用作用域插槽
(1) 父组件需要向子组件传递标签结构内容
(2) 但决定父组件传递怎样标签结构的数据在子组件中
1.1.2. 利用slot-scope能做什么呢?
1. 对于封装列表之类的组件特别需要
2. element-ui中: Table组件中就用到了slot-scope
1.1.3. 编码测试
需求: 封装列表List组件
效果一: 显示TODO列表时, 已完成的TODO为绿色
效果二: 显示TODO列表时, 带序号, TODO的颜色为蓝绿搭配
ScopeSlotTest.vue
<template> <div> <h2>效果一: 显示TODO列表时, 已完成的TODO为绿色</h2> <!-- 将todos传递给子组件list --> <!-- <List :todos="todos"> //子组件传递过来的数据(todo)被一个对象slotProps接收 <template slot-scope="slotProps"> <span v-if="slotProps.todo.isComplete" style="color:hotpink">{{slotProps.todo.text}}</span> <span v-else>{{slotProps.todo.text}}</span> </template> </List> --> <hr> <h2>效果二: 显示TODO列表时, 带序号, TODO的颜色为蓝绿搭配</h2> <List :todos="todos"> <template slot-scope="slotProps"> <span :style="{color:slotProps.index % 2===1?'blue':'green'}">{{slotProps.index+1}}{{slotProps.todo.text}}</span> </template> </List> </div> </template> <script type="text/ecmascript-6"> import List from './List' export default { name: 'ScopeSlotTest', data () { return { todos: [ {id: 1, text: 'AAA', isComplete: false}, {id: 2, text: 'BBB', isComplete: true}, {id: 3, text: 'CCC', isComplete: false}, {id: 4, text: 'DDD', isComplete: false}, ] } }, components: { List } } </script>
List.vue
<template> <ul> <li v-for="(todo, index) in todos" :key="todo.id"> <!-- 将todo传递给父组件 ,slot只是占位置,将父组件的模板占据过来,没有实际意义--> <slot :todo="todo" :index="index"> {{todo.text}} </slot> </li> </ul> </template> <script> export default { name: 'List', props: { todos: Array } } </script>
1.1. 组件间通信高级7: vuex
1. vuex用来统一管理多个组件共享的状态数据
2. 任意要进行通信的2个组件利用vuex就可以实现
(1) A组件触发action或mutation调用, 将数据保存到vuex的状态中
(2) B组件读取vuex中的state或getters数据, 得到最新保存的数据进行显示