目录
第一种:props
适用于的场景:父子组件通信
注意事项:
如果父组件给子组件传递数据(函数):本质其实是子组件给父组件传递数据
如果父组件给子组件传递的数据(非函数):本质就是父组件给子组件传递数据
书写方式:三种
['todos'], {type: Array}, {type: Array, default: []}
小提示:路由的props
书写形式:布尔值,对象,函数形式
第二种:自定义事件
适用于场景:子组件给父组件传递数据
$on和$emit
第三种:全局事件总线$bus
适用于场景:万能
Vue.prototype.$bus = this
第四种:pubsub-js, 在React框架中使用比较多(发布和订阅)
适用于场景:万能
第五种:Vuex
适用于场景:万能
第六种:插槽
适用于场景:父子组件通信(一般结构)
默认插槽
具名插槽
作用域插槽
第一章:自定义事件
1. 给button绑定原生DOMclick事件,触发handler函数
2. Event1是组件,非原生DOM节点,而绑定的click事件并不是原生DOM事件,而是自定义事件,要把它变成原生的DOM事件,要在click后面加上.native,实质上是给Event1组件的根节点绑定了点击事件,利用了事件委派
3. 给原生DOM绑定自定义事件,是没有任何意义的,因为无法触发$emit
4. 给组件Event2分别绑定了自定义事件click和xxx,在组件里面点击按钮触发自定义事件handler3,并把参数传过去
// Event.vue
<template>
<div>
<h1>event组件</h1>
<!-- 原生DOM绑定系统事件 -->
<button @click="handler">原生DOM绑定原生事件</button>
<!-- Event1组件:Event1非原生DOM节点,而绑定的click事件并非原生DOM事件,而是自定义事件
@click.native,可以把自定义事件变为原生DOM事件
当前原生DOMclick事件,其实是给子组件的根节点绑定了点击事件————利用了事件委派
-->
<Event1 @click.native="handler1"></Event1>
<!-- 下面的写法是给原生DOM绑定自定义事件
给原生DOM绑定自定义事件没有任何意义,因为没有办法触发$emit -->
<button @xxx="handler2">原生DOM绑定自定义事件</button>
<!-- 组件标签 -->
<Event2 @click="handler3" @xxx="handler3"></Event2>
</div>
</template>
<script>
import Event1 from '@/views/Event/event1.vue'
import Event2 from '@/views/Event/event2.vue'
export default {
name: 'MyEvent',
components:{
Event1,
Event2
},
methods:{
handler(event){
// 原生DOMbutton事件回调
console.log('原生DOM绑定原生事件', event);
},
handler1(){
console.log('event1事件回调');
},
handler2(){
},
// event自定义事件回调
handler3(params){
console.log('event自定义事件回调', params);
}
}
}
</script>
<style>
</style>
// event1.vue
<template>
<div>
<h2>Event1组件</h2>
<span>其它内容</span>
</div>
</template>
<script>
export default {
name: 'MyEvent1'
}
</script>
<style scoped>
div{
width: 100%;
height: 100px;
background-color: pink;
}
</style>
// event2.vue
<template>
<div>
<h2>Event2组件</h2>
<button @click="$emit('click', '自定义事件回调')">分发自定义click事件</button>
<button @click="$emit('xxx', '自定义事件xxx')">分发自定义xxx事件</button>
</div>
</template>
<script>
export default {
name: 'MyEvent2'
}
</script>
<style scoped>
div{
width: 100%;
height: 100px;
background-color: pink;
}
</style>
第二章:v-model深入
1. v-model实现原理:value与input事件实现的,而且还需要注意可以通过v-model实现父子组件数据同步
2. 通过input输入框的input事件,可以获取输入框中的文本内容,使用v-bind将data中的数据绑定到输入框中,再通过input事件每当文本框内容发生变化,就把输入框中的内容赋值给msg
3. 通过上面这个特征,可以让让父子组件通信。
1)通过 :value 将msg中的数据传给子组件
2)给组件定义自定义事件input,每当触发这个事件,就把传输过来的值赋给msg
3)CustomInput组件props接收传过来的数据,v-bind绑定到输入框中,再通过input事件触发自定义事件,将文本框中的内容传给父组件
4. v-model不仅可以使用在表单元素中,也可以利用上面这个特性来实现父子组件通信,所有在组件标签中直接使用v-model也是可以的
// Model.vue
<template>
<div>
<h2>深入v-model</h2>
<input type="text" v-model="msg">
<span>{{msg}}</span>
<hr>
<h2>v-model实现原理(vue2)</h2>
<!-- 原生DOM当中是有oninput事件,它经常结合表单元素一起使用,当表单元素文本内容发生变化的时候,就会触发一次回调
Vue2:可以通过value与input事件结合实现v-model功能-->
<input type="text" :value="msg" @input="msg = $event.target.value">
<span>{{msg}}</span>
<hr>
<!-- :value: 是props,父传子 @input:并非原生的DOM的input事件,属于自定义事件 -->
<CustomInput :value="msg" @input="msg = $event"></CustomInput>
<CustomInput v-model="msg"></CustomInput>
</div>
</template>
<script>
import CustomInput from '@/views/Model/CustomInput.vue'
export default {
name: 'MyModel',
components: {CustomInput},
data(){
return{
msg: 'abcdefg'
}
}
}
</script>
// CustomInput.vue
<template>
<div style="background: pink; height: 100px">
<h2>input包装组件</h2>
<!-- :value 动态属性 @input 给原生DOM绑定原生DOM事件 -->
<input type="text" :value="value" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
第三章:sync修饰符
1. props给子组件传递money,并且给子组件定义“update:money”自定义事件,子组件点击按钮后就会触发这个自定义事件,将变化的数据传递给父组件,更新数据,其实和v-model很相似,都是可以实现父子数据同步 ———— Child1组件
2. 在通过props传递数据的时候,在后面加上.sync,有两个作用,第一个是传递数据,第二个是绑定自定义事件,比如下面给Child2绑定的自定义事件叫做“update:money”,与上面是同样的效果,也实现了父子数据同步 ———— Child2组件
// Sync.vue
<template>
<div>
<h1>小明的爸爸现在有{{money}}元</h1>
<h2>不使用sync修饰符</h2>
<!-- :money 父组件给子组件传递props
@update:money 给子组件绑定的自定义事件只不过名字叫做update:money
目前这种操作,和v-model很相似,可以实现父子数据同步 -->
<Child1 :money="money" @update:money="money = $event"></Child1>
<h2>使用sync修饰符</h2>
<!-- :money.sync 第一:父组件给子组件传递props money, 第二:给当前子组件绑定了一个自定义事件,而且事件名称即为update:money -->
<Child2 :money.sync="money"></Child2>
</div>
</template>
<script>
import Child1 from '@/views/Sync/Child1.vue'
import Child2 from '@/views/Sync/Child2.vue'
export default {
name: 'MySync',
components:{
Child1,
Child2
},
data(){
return{
money: 10000
}
}
}
</script>
<style>
</style>
// Child1.vue
<template>
<div style="background: #ccc; height: 50px">
<span>小明每次花100元</span>
<button @click="$emit('update:money', money - 100)">花钱</button>
爸爸还剩{{money}}元
</div>
</template>
<script>
export default {
name: 'MyChild1',
props: ['money']
}
</script>
// Child2.vue
<template>
<div style="background: #ccc; height: 50px">
<span>小明每次花100元</span>
<button @click="$emit('update:money', money - 100)">花钱</button>
爸爸还剩{{money}}元
</div>
</template>
<script>
export default {
name: 'MyChild2',
props: ['money']
}
</script>
第四章:$attrs 和 $listeners
1. $listeners与$attrs(组件通信方式一种), 它们两者都是组件实例的属性,可以获取到父组件给子组件传递props与自定义事件
2. $attrs属于组件的一个属性,可以获取到父组件传递过来的props数据
3. 对于子组件而言,父组件给的数据可以利用props接收,但是如果子组件通过props接收的数据,在$attrs属性中获取不到
4. $listeners,他也是组件实例自身的一个属性,它可以获取父组件给子组件传递的自定义事件
5. 在子组件中,如果要使用$attrs,则必须用v-bind,不能使用: ,如果要使用$listeners,必须使用v-on,不能使用@
// AttrsListeners.vue
<template>
<div>
<h2>自定义带hover提示的按钮</h2>
<!-- 当用户使用封装的按钮的时候, 需要向HintButton组件传递相应的参数, -->
<!-- @click, 表示自定义事件 -->
<HintButtonVue type="primary" icon="el-icon-edit" size="mini" title="按钮" @click="handler"></HintButtonVue>
</div>
</template>
<script>
import HintButtonVue from '@/views/HintButton/HintButton.vue'
export default {
name: 'AttrsListeners',
components:{ HintButtonVue },
methods:{
handler(){
console.log('触发handler函数');
}
}
}
</script>
<style>
</style>
// HintButton.vue
<template>
<div>
<a :title="title"></a>
<!-- v-bind不能用: , v-on 不能用 @ -->
<el-button v-bind="$attrs" v-on="$listeners"></el-button>
</div>
</template>
<script>
export default {
props: ['title'],
mounted(){
// $attrs属于组件的一个属性,可以获取到父组件传递过来的props数据
// 对于子组件而言,父组件给的数据可以利用props接收,但是如果子组件通过props接收的数据,在$attrs属性中获取不到
console.log(this.$attrs);
// $listeners,他也是组件实例自身的一个属性,它可以获取父组件给子组件传递的自定义事件
console.log(this.$listeners);
}
}
</script>
<style>
</style>
第五章:$children与$parent
1. ref可以获取到某一个组件,子组件给父组件传递数据
2. $children组件实例的属性,可以获取当前组件的全部子组件[数组]
3. $parent组件实例的属性,可以获取当前子组件的父组件,进而可以操作父组件的数据与方法
4. 下面这个案例,BABA找小明和小红借钱,可以通过$children获取它们的组件实例对象,进而操作数据
5. 小明和小红给BABA前,可以通过$parent获取父组件实例对象,操作数据
6. 通过这两个方法也可以实现父子组件之间的通信
注意:获取子组件实例对象时不要使用 this.$children[0] 这个样式,因为如果组件多的话顺序不一定
// ChildrenParent.vue
<template>
<div>
<h2>BABA有存款:{{money}}</h2>
<button @click="borrowMoneyFromXM(100)">找小明借钱100</button>
<button @click="borrowMoneyFromXH(150)">找小红借钱150</button>
<button @click="borrowMoneyFromAll(200)">找所有孩子借钱200</button>
<br>
<Son ref="xm"></Son>
<br>
<Daughter ref="xh"></Daughter>
</div>
</template>
<script>
import Son from '@/views/ChildrenParent/Son.vue'
import Daughter from '@/views/ChildrenParent/Daughter.vue'
export default {
name: 'ChildrenParent',
components:{
Son, Daughter
},
data(){
return {
money: 1000
}
},
methods:{
borrowMoneyFromXM(money){
this.money += money
// ref可以获取真实DOM节点,也可以获取子组件标签(操作子组件的数据与方法)
this.$refs.xm.money -= money
},
borrowMoneyFromXH(money){
this.money += money
this.$refs.xh.money -= money
},
borrowMoneyFromAll(money){
this.money += 2 * money
this.$children.forEach(item => {
item.money -= money
})
}
}
}
</script>
// Son.vue
<template>
<div style="background: pink; height: 70px;">
<h3>儿子小明,有存款{{money}}</h3>
<button @click="lendMoney(50)">给BABA钱:50</button>
</div>
</template>
<script>
export default {
name: 'MySon',
data(){
return{
money: 3000
}
},
methods:{
lendMoney(money){
this.money -= money
// 可以通过$parent属性获取到某一个组件的父组件, 进而操作父组件的数据与方法
this.$parent.money += money
}
}
}
</script>
<style>
</style>
// Daughter.vue
<template>
<div style="background: pink; height: 70px">
<h3>女儿小红,有存款{{ money }}</h3>
<button @click="lendMoney(100)">给BABA钱:100</button>
</div>
</template>
<script>
export default {
name: 'MyDaughter',
data() {
return {
money: 2000,
}
},
methods: {
lendMoney(money) {
this.money -= money
this.$parent.money += money
},
},
}
</script>
<style></style>
第六章:作用域插槽
1.插槽:可以实现父子组件通信(通信的结构)
2. 作用域插槽:子组件来源于父组件,但是子组件决定不了它的外观和结构
3. 在下面两个案例中,父组件将todos传给子组件,子组件决定不了它的外观,将数组的每一项传给父组件,定义作用域插槽,在父组件中定义它们的样式
//SlotScope.vue
<template>
<div>
<h2>效果一:显示TODO列表时,已完成的TODO为绿色</h2>
<List :todos="todos">
<template slot-scope="todo">
<span :style="{color: todo.todo.isComplete ? 'green' : 'red'}">{{todo.todo.text}}</span>
</template>
</List>
<hr>
<h2>效果二:显示TODO列表时,带序号,TODO的颜色为蓝绿搭配</h2>
<List1 :todos="todos">
<template slot-scope="{todo, $index}">
<span :style="{color: todo.isComplete ? 'pink' : 'purple'}">{{$index}} --- {{todo.text}}</span>
</template>
</List1>
</div>
</template>
<script>
import List from '@/views/ScopeSlot/List.vue'
import List1 from '@/views/ScopeSlot/List1.vue'
export default {
name: 'ScopeSlot',
components:{ List, List1 },
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 },
]
}
}
}
</script>
<style>
</style>
// List.vue
<template>
<ul>
<li v-for="(item, index) in todos" :key="index">
<slot :todo="item"></slot>
</li>
</ul>
</template>
<script>
export default {
name: 'MyList',
props:{
todos: Array
}
}
</script>
// List1.vue
<template>
<ul>
<li v-for="(item, index) in todos" :key="index">
<slot :todo="item" :$index="index"></slot>
</li>
</ul>
</template>
<script>
export default {
name: 'MyList',
props:{
todos: Array
}
}
</script>