Vue 组件通信
Vscode 小知识
补充:vs 单击文件是预览模式,点开一个文件在点开其他文件会覆盖当前文件浏览窗口,可以双击文件进入编辑模式就可以解决问题
一、常规父子组件通信
1、Props
props 实现父向子传递消息
常用的方式:
使用
props
在父子组件之间传递消息props
可以绑定函数或者数据子组件需要接收消息,然后使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>父子组件通信</title>
</head>
<body>
<div id="app">
<father></father>
</div>
</body>
<script src="js/vue.js"></script>
<script>
// 父向子传递数据(父 -> 子 单向)
// 父向子传递函数(子 -> 父 单向)
const Child1 = Vue.extend({
name: 'Child1',
props: {
receiveMsg: {
type: String,
default: ''
},
receiveFunction: {
type: Function,
required: true
}
},
created() {
console.log('---------- vc_child1 ----------')
console.log(this)
},
data() {
return {
step: 1
}
},
methods: {
add() {
this.receiveFunction(++this.step)
}
},
template: `
<div>
<p>这里是子组件 1</p>
<p>从父组件传过来的数据: {{ receiveMsg }}</p>
<button @click="add">点我增加父组件中的计数器</button>
<hr/>
</div>
`
})
// 父组件
const Father = Vue.component('father', {
name: 'Father',
components: {Child1},
data() {
return {
// 向子组件传递消息
msg1: '传递消息',
// 计数器
count: 0
}
},
methods: {
increase(step) {
this.count += step
}
},
created() {
console.log('---------- vc_father ----------')
console.log(this)
},
template: `
<div>
<p>这里是父组件</p>
<label for="send">发送给子组件的消息</label>
<input id="send" v-model="msg1">
<p>父组件中用于计数的值 {{ count }}</p>
<hr/>
<child1 :receive-function="increase" :receive-msg="msg1"></child1>
</div>
`
})
new Vue({
el: '#app',
created() {
console.log('---------- vm ----------')
console.log(this)
},
})
</script>
</html>
2、自定义事件
自定义事件实现子向父传递消息
自定义事件
- 适用于子组件给父组件传递数据
- 父组件中给组件绑定自定义事件(实际上子组件回调的时候是在 vc 的原型上找到这个事件方法的)
- 子组件使用
this.$emit(对应的方法, 参数)
自定义事件两种方式:
使用
this.$emit
(推荐)<Header @add-todo="addTodo"/> // 注意自定义事件的命名方式 // 子组件中使用,需要定义一个新的方法接收这个自定义事件,点击这个新方法就会执行父组件的回调,从而实现子组件向父组件传递消息 methods: { add() { if (!this.name.trim()) return alert('输入不能为空') // 根据用户的输入生成一个 todo 对象 const todoObj = { id: Date.now(), name: this.name, done: false } // 使用自定义事件通知 App 在 data 中添加一个 todo this.$emit('add-todo', todoObj) // 清空输入 this.name = '' } }, // props: [ 'addTodo' ] }
父组件使用
ref
标记子组件,然后在mounted
中使用this.$refs.demo.$on('test', this.test)
为子组件绑定自定义事件,子组件调用的方法和第一种方式一样自定义事件的命名,使用 kebab-case 模式 例如:
get-data
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义事件</title>
</head>
<body>
<div id="app">
<father></father>
</div>
</body>
<script src="js/vue.js"></script>
<script>
// 子组件给父组件传递数据使用自定义事件 (子 -> 父 单向)
// 方式一: 使用 this.$emit 实现
const Child1 = Vue.extend({
name: 'Child1',
data() {
return {
msg: ''
}
},
methods: {
add() {
if (!this.msg.trim())
return alert('消息不能为空!')
this.$emit('send-msg', this.msg)
this.msg = ''
}
},
template: `
<div>
<p>这里是子组件 1</p>
<input v-model="msg" type="text" placeholder="输入信息, 点击按钮进行发送"> <button @click="add">发送</button>
<hr/>
</div>
`
})
// 方式二: 使用 ref 实现
const Child2 = Vue.extend({
name: 'Child2',
data() {
return {
msg: ''
}
},
methods: {
add() {
if (!this.msg.trim())
return alert('消息不能为空!')
this.$emit('send-msg', this.msg)
this.msg = ''
}
},
template: `
<div>
<p>这里是子组件 2</p>
<input v-model="msg" type="text" placeholder="输入信息, 点击按钮进行发送"> <button @click="add">发送</button>
<hr/>
</div>
`
})
// 父组件
const Father = Vue.component('father', {
name: 'Father',
components: { Child1, Child2 },
data() {
return {
msgList: []
}
},
methods: {
receiveMsg(msg) {
this.msgList.push(msg)
}
},
mounted() {
this.$refs.child2_ref.$on('send-msg', this.receiveMsg)
},
template: `
<div>
<p>这里是父组件</p>
<label>接收子组件传递的消息列表:</label>
<ul>
<li v-if="msgList.length === 0">暂无消息</li>
<li v-else v-for="msg in msgList">{{ msg }}</li>
</ul>
<hr/>
<child1 @send-msg="receiveMsg"></child1>
<child2 ref="child2_ref"></child2>
</div>
`
})
new Vue({
el: '#app'
})
</script>
</html>
二、任意组件通信
全局事件总线
全局事件总线可以实现任意组件之间的通信
它的原理就是当一个事物可以被所有组件都看到,并且我们可以为它绑定自定义事件,那么借助这个工具就可以实现任意组件相互通信。
Q:什么东西可以被所有组件看到呢?
A:Vue 的原型:Vue.prototype 可以被所有组件看到
所以:将自定义事件绑定到 Vue 的原型上
main.js
中注册:
安装全局事件总线:
import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false // 定义全局事件总线 new Vue({ beforeCreate() { // 注意这一步比较巧妙 Vue.prototype.$bus = this // 安装事件总线 }, el: '#app', render: h => h(App) })
在 Demo1 中给 $bus 绑定自定义事件
methods: { test1(x) { console.log('Demo1 收到了数据', x) this.name = x }, }, mounted() { // 给 $bus 绑定一个自定义事件 this.$bus.$on('test1', this.test1) // 这里要注意自定义函数时,this 的指向问题,test1 如果使用 function 定义,则 this 指向 $bus }, beforeDestroy() { // 关闭自定义事件 this.$bus.$off('test1') },
在 Demo2 中调用 $bus 上绑定的事件,实现与 Demo1 通信
methods: { sendData() { this.$bus.$emit('test1', 'NaiveKyo') } }
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>全局事件总线</title>
</head>
<body>
<div id="app">
<Child1></Child1>
<Child2></Child2>
<Child3></Child3>
</div>
</body>
<script src="js/vue.js"></script>
<script>
// 任意组件通信
const Child1 = Vue.extend({
name: 'Child1',
data() {
return {
msgList: []
}
},
methods: {
receiveMsg(msg) {
this.msgList.push(msg)
},
sendMsgToComponent2() {
this.$bus.$emit('child-2', '这是来自组件 1 的消息')
},
sendMsgToComponent3() {
this.$bus.$emit('child-3', '这是来自组件 1 的消息')
}
},
mounted() {
// 注册事件
this.$bus.$on('child-1', this.receiveMsg)
},
beforeDestroy() {
// 关闭自定义事件
this.$bus.off('child-1')
},
template: `
<div>
<p>这里是组件 1</p>
<label>接收其他组件传递的消息列表:</label>
<ul>
<li v-if="msgList.length === 0">暂无消息</li>
<li v-else v-for="msg in msgList">{{ msg }}</li>
</ul>
<button @click="sendMsgToComponent2">给组件 2 发送消息</button>
<button @click="sendMsgToComponent3">给组件 3 发送消息</button>
<hr/>
</div>
`
})
const Child2 = Vue.extend({
name: 'Child2',
data() {
return {
msgList: []
}
},
methods: {
receiveMsg(msg) {
this.msgList.push(msg)
},
sendMsgToComponent1() {
this.$bus.$emit('child-1', '这是来自组件 2 的消息')
},
sendMsgToComponent3() {
this.$bus.$emit('child-3', '这是来自组件 2 的消息')
}
},
mounted() {
// 注册事件
this.$bus.$on('child-2', this.receiveMsg)
},
beforeDestroy() {
// 关闭自定义事件
this.$bus.off('child-2')
},
template: `
<div>
<p>这里是组件 2</p>
<label>接收其他组件传递的消息列表:</label>
<ul>
<li v-if="msgList.length === 0">暂无消息</li>
<li v-else v-for="msg in msgList">{{ msg }}</li>
</ul>
<button @click="sendMsgToComponent1">给组件 1 发送消息</button>
<button @click="sendMsgToComponent3">给组件 3 发送消息</button>
<hr/>
</div>
`
})
const Child3 = Vue.extend({
name: 'Child3',
data() {
return {
msgList: []
}
},
methods: {
receiveMsg(msg) {
this.msgList.push(msg)
},
sendMsgToComponent1() {
this.$bus.$emit('child-1', '这是来自组件 3 的消息')
},
sendMsgToComponent2() {
this.$bus.$emit('child-2', '这是来自组件 3 的消息')
}
},
mounted() {
// 注册事件
this.$bus.$on('child-3', this.receiveMsg)
},
beforeDestroy() {
// 关闭自定义事件
this.$bus.off('child-3')
},
template: `
<div>
<p>这里是组件 3</p>
<label>接收其他组件传递的消息列表:</label>
<ul>
<li v-if="msgList.length === 0">暂无消息</li>
<li v-else v-for="msg in msgList">{{ msg }}</li>
</ul>
<button @click="sendMsgToComponent1">给组件 1 发送消息</button>
<button @click="sendMsgToComponent2">给组件 2 发送消息</button>
<hr/>
</div>
`
})
new Vue({
beforeCreate() {
// 安装事件总线
Vue.prototype.$bus = this
},
el: '#app',
components: { Child1, Child2, Child3 }
})
</script>
</html>
三、插槽
此方式用于父组件向子组件传递带数据的标签,当一个组件有不确定的结构时,就需要使用 slot
技术了,注意:插槽内容是在父组件中编译后,再传递给子组件的。
分类
- 默认插槽
- 命令插槽
- 作用域插槽
- 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
四、补充
消息订阅与发布
npm install pubsub-js