我们已经学过的组件通信方式 6 种
props
适用于 父子组件通信
如果父组件给 子组件传递数据(传递的是一个函数),本质是子给父传递数据。
如果父组件给 子组件传递数据(不是函数的情况),本质是父给子传递数据。
书写方式:
1 2 3 4 5 6
| ['tools']
[type:Array]
{type:Array, default:[]}
|
自定义事件
组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
绑定自定义事件:
第一种方式,在父组件中:<Demo @atguigu="test"/>
或 <Demo v-on:atguigu="test"/>
具体代码
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <template> <div class="app"> <Student @atguigu="getStudentName"/> </div> </template>
<script> import Student from './components/Student'
export default { name:'App', components:{Student}, data() { return { msg:'你好啊!', studentName:'' } }, methods: { getStudentName(name,...params){ console.log('App收到了学生名:',name,params) this.studentName = name } } } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
Student.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <div class="student"> <button @click="sendStudentlName">把学生名给App</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'张三', } }, methods: { sendStudentlName(){ this.$emit('atguigu',this.name,666,888,900) } }, } </script>
<style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
|
第二种方式,在父组件中:
使用 this.$refs.xxx.$on()
这样写起来更灵活,比如可以加定时器啥的。
具体代码
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div class="app"> <Student ref="student"/> </div> </template>
<script> import Student from './components/Student'
export default { name:'App', components:{Student}, data() { return { studentName:'' } }, methods: { getStudentName(name,...params){ console.log('App收到了学生名:',name,params) this.studentName = name }, }, mounted() { this.$refs.student.$on('atguigu',this.getStudentName) }, } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
Student.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <template> <div class="student"> <button @click="sendStudentlName">把学生名给App</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'张三', } }, methods: { sendStudentlName(){ this.$emit('atguigu',this.name,666,888,900) } }, } </script>
<style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
|
若想让自定义事件只能触发一次,可以使用once
修饰符,或$once
方法。
若想给子组件绑定原生的事件,可以后面加上 native
修饰符。
触发自定义事件:this.$emit('atguigu',数据)
使用 this.$emit() 就可以子组件向父组件传数据
解绑自定义事件this.$off('atguigu')
代码
组件上也可以绑定原生DOM事件,需要使用native
修饰符。
代码
1 2
| <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) --> <Student ref="student" @click.native="show"/>
|
注意:通过this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
全局事件总线 $bus
使用场景:万能
Vue.prototype.$bus = this;
pubsub-js 发布与订阅
使用场景:万能
vuex
使用场景:数据非持久化—-万能的
核心概念:5个
state
mutations
actions
getters
modules
插槽
使用场景:父子组件通信
默认插槽
具名插槽
作用域插槽
组件间通信高级
(非常重要, 面试必备)
event深入
- 原生DOM–button 可以绑定系统事件,click单击事件等。
- 组件标签—可以绑定系统事件,不起作用,因为属于自定义事件了。
可以加上 .native 变成原生dom事件,其实就是给子组件的上的那个div根标签绑定了事件了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <template> <div> <h1>EventTest组件</h1><br/><br/> <!-- 1. 原生DOM--button 可以绑定系统事件,click单击事件等。 2. 组件标签---<Event1>可以绑定系统事件,不起作用,因为属于自定义事件了。 可以加上 .native 变成原生dom事件,其实就是给子组件的上的那个div根标签绑定了事件了。 -->
<!-- 原生DOM事件 --> <button @click="handler">原生btn按钮</button><br/><br/> <!-- 使用Event1组件:底下这个组件 @click.native 原生DOM事件,利用事件的委派--> <!-- 下面组件上 如果 写成@click 就会当成自定义事件了,需要 emit 来触发,这时候点击的时候根本不会执行 -->
<Event1 @click.native="handler1"></Event1><br/> <hr/> <!-- 自定义事件对于原生DOM没有任何意义 给原生DOM绑定自定义事件是没有意义的!! --> <!-- <button @erha="handler3"> 原生的btn</button> -->
<!-- 这里的 @click="handler2" 属于自定义事件,能够触发是因为子组件里面有个button点击后会调用 $emit('click',66666) 这个 后面的 @xxx="handler2" 也是个自定义事件,也是子组件里面有 调用 $emit('xxx',77777) --> <Event2 @click="handler2" @xxx="handler2"></Event2><br/><br/> <!-- 表单元素 color:选取颜色 range:范围条 date:日历 week--> <input type="week" /> </div> </template>
<script type="text/ecmascript-6"> import Event1 from './Event1.vue' import Event2 from './Event2.vue'
export default { name: 'EventTest',
components: { Event1, Event2, },
methods: { //原生DOM事件的回调 handler(event){ console.log("原生DOM事件的回调", event); //控制台就会打印: // 原生DOM事件的回调 PointerEvent {isTrusted: true, pointerId: 1, width: 1, height: 1, pressure: 0, …} }, handler1(){ console.log('66666666'); }, handler2(params){ console.log(params); } } } </script>
|
Event1.vue
1 2 3 4 5 6 7
| <template> <div style="background: #ccc; height: 80px;"> <h2>Event1组件</h2> <span>其它内容</span> </div> </template>
|
Event2.vue
1 2 3 4 5 6 7 8
| <template> <div style="background: #ccc; height: 80px;"> <h2>Event2组件</h2> <button @click="$emit('click',66666)">分发自定义click事件</button><br> <button @click="$emit('xxx',77777)">分发自定义xxx事件</button><br> </div> </template>
|
v-model深入
实现数据的双向绑定,多用于表单数据的收集。
vue中v-model和:value(即:v-bind:value)
区别:
v-model 实现了视图和data中数据的双向绑定,两者其一改变均改变
v-bind:value 只是将初始化时data中的数据绑定到input上,修改input中的值不会改变data中的数据。
v-model 本质其实是一个语法糖,背后是 v-bind:value 和 v-on:input 2个操作
这里介绍是vue3的组件上的 v-model
https://cn.vuejs.org/guide/components/v-model.html
这里介绍是vue2的组件上的 v-model
https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div> <h2>深入v-model</h2> <input type="text" v-model="msg"> <span>{{msg}}</span> <br> <h2>深入v-model原理</h2> <!-- 原生DOM当中是有oninput事件:当表单元素发生文本的变化的时候就会立即出发 。 这个实现就类似了 v-model 指令了。 下面这个 :value 是什么呢? 原生DOM绑定的动态数据v-bind的缩写。 --> <input type="text" :value="msg" @input=" msg = $event.target.value"/> <span>{{msg}}</span> <!--并非原生DOM:自定义组件, 实现父子组件通信了。 下面的这个 :value 是什么?这是props。 下面的 @input 是什么? 自定义事件。 --> <CustomInput :value="msg" @input="msg = $event"></CustomInput> <!-- 上面的写法实现,可以简写下面 --> <CustomInput v-model="msg"></CustomInput> <hr> </div> </template>
<script type="text/ecmascript-6"> import CustomInput from './CustomInput.vue' export default { name: 'ModelTest', data() { return { msg:"我爱塞北的大雪呀" } }, components: { CustomInput } } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div style="background: #ccc; height: 50px;"> <h2>input包装组件----{{value}}</h2> <input :value="value" @input="$emit('input',$event.target.value)"/> </div> </template>
<script type="text/ecmascript-6"> export default { name: 'CustomInput', props:['value'] } </script>
|
属性修饰符sync
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div> 小明的爸爸现在有{{ money }}元 <h2>不使用sync修改符</h2> <!-- 下面这里的 :money="money" 父组件通过props传递 数据给子组件 下面这里的 @update:money 这是个自定义事件,只是这个事件名称比较特殊,有个冒号什么的。 $event 也是不能随便起名的。 --> <Child :money="money" @update:money="money = $event"></Child> <h2>使用sync修改符</h2> <!-- 加上sync修饰符,就是上面的一个简写方式。 :money.sync 代表 父组件给子组件传递 props是money, 同时给子组件绑定一个自定义事件 update:money --> <Child :money.sync="money"></Child> <h2>使用v-model修改符</h2> <hr /> </div> </template>
<script type="text/ecmascript-6"> import Child from './Child.vue' import Child2 from './Child2.vue' export default { name: 'SyncTest', data() { return { money: 10000 } }, components: { Child, Child2 } } </script>
|
Child.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <div style="background: #ccc; height: 50px;"> <span>小明每次花100元</span> <button @click="$emit('update:money',money - 100)">花钱</button> 爸爸还剩 {{money}} 元 </div> </template>
<script type="text/ecmascript-6"> export default { name: 'Child', props:['money'] } </script>
|
Child2.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div style="background: #ccc; height: 50px;"> <span>小明每次花100元</span> <button>花钱</button> 爸爸还剩 ??? 元 </div> </template>
<script type="text/ecmascript-6"> export default { name: 'Child2' } </script>
|
$attrs与$listeners
传递静态或动态 Prop
https://v2.cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E9%80%92%E9%9D%99%E6%80%81%E6%88%96%E5%8A%A8%E6%80%81-Prop
像这样,你已经知道了可以像这样给 prop 传入一个静态的值:
1
| <blog-post title="My journey with Vue"></blog-post>
|
你也知道 prop 可以通过 v-bind 动态赋值,缩写 : 来进行动态绑定的 props:
例如:
1 2 3 4 5 6 7
| <!-- 动态赋予一个变量的值 --> <blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 --> <blog-post v-bind:title="post.title + ' by ' + post.author.name" ></blog-post>
|
在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div> <h2>自定义带Hover提示的按钮</h2> <!-- 二次封装的HintButton按钮的时候,把人家el-button需要的数据传递过去 --> <HintButton type="success" icon="el-icon-plus" title="我是中国人" @click="handler"/> </div> </template>
<script type="text/ecmascript-6"> import HintButton from './HintButton'; export default { name: 'AttrsListenersTest', components:{ HintButton }, methods: { handler() { alert('弹弹弹'); }, }, } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div> <a :title="title"> <!-- 这里使用 v-bind="$attrs" (这里不能用简写方式)接收父组件的所有props,除了 props:['title'] 这里提到的。 不这么做的话:如果props很多 就需要一个一个的写上了:size="$attrs.size" --> <el-button :size="$attrs.size" :icon="$attrs.icon" :type="$attrs.type" >添加</el-button>
<!-- v-on 不能简写。 --> <el-button v-bind="$attrs" v-on="$listeners">添加</el-button> </a> </div> </template>
<script> export default { name: "",
props:['title'], mounted(){ //this.$attrs:可以获取到父亲传递的数据【props】 //this.$attrs是可以获取父亲传递的props数据,如果子组件通过 //props:[],接受,this.$attrs属性是获取不到的 console.log(this.$attrs);
//组件实例上的属性。 可以获取父组件给子组件传递的自定义事件。 console.log(this.$listeners); } }; </script>
<style scoped></style>
|
$children 与 $parent
ref 可以获取到某一个子组件
$children 组件实例属性,可以获取当前组件的全部子组件【数组】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <template> <div> <h2>BABA有存款: {{ money }}</h2> <button @click="JieQianFromXM(100)">找小明借钱100</button><br /> <button @click="JieQianFromXH(150)">找小红借钱150</button><br /> <button @click="JieQianAll(200)">找所有孩子借钱200</button><br /> <button @click="SendInfo">我是baba</button> <br /> <Son ref="xm" /> <br /> <Daughter ref="xh"/> </div> </template>
<script> import Son from "./Son"; import Daughter from "./Daughter";
export default { name: "ChildrenParentTest", data() { return { money: 1000, }; },
methods: { JieQianFromXM(money) { this.money += money; this.$refs.xm.money -= money; }, JieQianFromXH(money) { this.money += money; this.$refs.xh.money -= money; }, JieQianAll(money){ this.money += 2*money;
this.$children.forEach(item=>item.money-=money);
}, SendInfo(){ this.$refs.xm.tinghua(); } },
components: { Son, Daughter, }, }; </script>
<style></style>
|
Son.vue 子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <template> <div style="background: #ccc; height: 50px;"> <h3>儿子小明: 有存款: {{money}}</h3> <button @click="geiQian(50)">给BABA钱: 50</button> </div> </template>
<script> export default { name: 'Son', data () { return { money: 30000 } },
methods: { tinghua(){ console.log('我是小明,我听爸爸的话'); }, geiQian(money){ this.money-=money; this.$parent.money+=money; } } } </script>
|
Daughter.vue 子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div style="background: #ccc; height: 50px;"> <h3>女儿小红: 有存款: {{money}}</h3> <button @click="geiQian(100)">给BABA钱: 100</button> </div> </template>
<script> export default { name: 'Daughter', data () { return { money: 20000 } },
methods: { geiQian(money){ this.money-=money; this.$parent.money+=money; } } } </script>
|
mixin 混入
1 2 3 4 5 6 7 8 9 10 11
| export default { // 对外暴露的对象,可以放置组件重复的js业务逻辑 methods: { // 2个子组件这个方法是重复,可以单独提取出来 geiQian(money) { this.money -= money; this.$parent.money += money; } }
}
|
引入 mixin 之后 Daughter.vue 子组件 中的 methods 重复的函数就可以省略不写了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div style="background: #ccc; height: 50px;"> <h3>女儿小红: 有存款: {{ money }}</h3> <button @click="geiQian(100)">给BABA钱: 100</button> </div> </template>
<script> import MyMixin from './mixin'
export default { name: 'Daughter',
data() { return { money: 20000 } },
mixins:[MyMixin],
methods: {} } </script>
|
作用域插槽scope-slot
父子组件通信的,主要通信的是结构。 结构在父组件中定义,可以让子组件 显示不同的结构,
例如是 a标签展示,span 标签展示等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <template> <div> <h2>效果一: 显示TODO列表时, 已完成的TODO为绿色</h2> <!-- 子组件,数据来自父组件。。。 --> <List :todos="todos"> <!-- 书写template, 子组件决定不了结构和外观 , 这里你是用h标签展示,还是用span标签展示,都由父组件这里决定了 --> <template slot-scope="todo"> <h5 :style="{color:todo.todo.isComplete?'green':'black'}">{{todo.todo.text}}</h5> </template> </List> <List :todos="todos"> <!-- 书写template --> <template slot-scope="todo"> <a :style="{color:todo.todo.isComplete?'green':'black'}">{{todo.todo.text}}</a> </template> </List> <hr> <h2>效果二: 显示TODO列表时, 带序号, TODO的颜色为蓝绿搭配</h2> <List1 :data="todos"> <template slot-scope="{row,index}"> <h1 :style="{color:row.isComplete?'green':'hotpink'}">索引值{{index}}---------{{row.text}}</h1> </template> </List1> </div> </template>
<script type="text/ecmascript-6"> //子组件 import List from './List' import List1 from './List1' 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, List1 } } </script>
|
List.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <ul> <li v-for="(todo,index) in todos" :key="index"> <!-- 坑:熊孩子挖到坑,父亲填坑 --> <!-- 数据来源于父亲:但是子组件决定不了结构与外关,这里还需 :todo="todo" 数据回传给父组件 那个slot的地方。slot-scope="todo"这样来接收数据 --> <slot :todo="todo"></slot> </li> </ul> </template>
<script> export default { name: 'List', props: { todos: Array } } </script>
|
List1.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <ul> <li v-for="(todo,index) in data" :key="index"> <!-- 坑:熊孩子挖到坑,父亲填坑 --> <!-- 数据来源于父亲:但是子组件决定不了结构与外观, 这里回传了2个数据 :row="todo" :index="index "。 这样来接收 slot-scope="{todo,index}" 数据(当回传多个数据时候, 这里是一个对象,在接收的地方 使用 解构赋值,key 必须对应一致) --> <slot :row="todo" :index="index "></slot> </li> </ul> </template>
<script> export default { name: 'List1', props: { data: Array } } </script>
|