在 Vue 中,每一个组件都是独立的一个存在,如果需要组合使用这些组件,必然会将组件间的数据相互关联在一起,这种就叫做组件间的通信。
例如: 有两个组件(A,B),在 B 组件中需要使用A 组件的数据,又或者在 A 组件中需要用到 B 组件的数据,类似于这种场景就会涉及到组件间的通信,也可以叫做数据传递!
创建组件
要想做通信这个事情,首先要有两个组件,下来我们就创建一下吧,后续内容我们会在此基础上进行修改。
首先我们需要创建一个 Vue 实例,可以把这个实例当做是 父组件
<div id="#app"></div>
<script>
const vm = new Vue({
el: '#app'
})
</script>
然后我们还需要一个子组件,这里就创建一个全局组件吧!需要注意的是 组件必须在Vue实例前面定义
,
第一个参数是组件的名字
,第二个参数是组件的一些配置
。
第一个参数可以把模板直接写在组件内部,也可以写在组件的外部。
第二个参数中,可以配置像 Vue 实例中的属性
,例如: data、methods、生命周期钩子函数....
Vue.component("component-child", {
template: `<h2>This is child component...</h2>`,
// data 、 methods、 生命周期钩子函数...都可以在组件中定义
})
// Vue 实例...
模板写在组件外部
<script type="text/template" id="childTemplate">
// 注意: 在这里需要包一层 `div` 组件中只允许有一个根节点.
<div>
// 这里面写组件的内容
<div>
</script>
<!-- 使用组件模板时直接把 id 献上就可以啦 -->
<script>
Vue.component("component-child", {
template: '#childTemplate'
// data 、 methods、 生命周期钩子函数...都可以在组件中定义
})
</script>
使用组件就非常简单了,直接在 html 中使用即可.
<div id="#app">
<!-- 使用组件 -->
<component-child></component-child>
</div>
需要用的组件就创建完了,接下来我们可以在浏览器中看一哈我们创建的组件吧!
组件通信有以下几种方式
一、props、$emit
使用
props
可以在子组件里接收父组件传递过来的数据
我们先在 Vue 实例 也就是 父组件
中定义一些数据
const vm = new Vue({
el: '#app',
// 以下是新增的内容
data: {
user: { // 用户信息
username: 'xiaosai',
age: 18
},
cities: ['北京', '上海', '广州', '深圳'] // 城市列表
}
})
要把数据传递给子组件
,需要在组件上通过标签属性
的方式传递,修改标签如下
<div id="app">
<!-- 这里 v-bind 可以省略,简写成 : -->
<component-child v-bind:user="user" v-bind:cities="cities"></component-child>
</div>
修改子组件,在子组件中定义 props
属性,用来接收父组件传递过来的数据
Vue.component("component-child", {
props: ['user', 'cities'], // 这是新增的内容,这里的值要和上图中的属性一致哦~这样才能拿到数据
template: `<h2>This is child component...</h2>`,
})
拿到数据后就可以随意使用啦~~~~ 修改子组件模板,把数据渲染出来
Vue.component("component-child", {
props: ['user', 'cities'],
template: `
<div>
<p>用户信息: {{user.username}} 今年 {{user.age}} 岁</p>
<ul>
<li v-for="(c,index) in cities" :key="index">{{c}}</li>
</ul>
</div>
`,
})
结果
使用
$emit
这种方式可以把子组件中的数据传递到父组件中.
我们已经知道了怎么从子组件中获取父组件的数据,那么如何在父组件中获取和使用子组件的数据呢,这时候我们可以使用 $emit
来做.
$emit 实际上是 Vue 实例的一个方法,它可以向外派发一个事件,把数据绑定到这个事件上。通过在标签上监听这个事件来获取数据。
Vue.component("component-child", {
template: `
// 当点击按钮的时候,通过 $emit 派发一个自定义事件 my-event,把 data 中的 msg 数据传递出去
<button @click="$emit('my-event',msg)">clickMe</button>
`,
data() {
return {
msg: 'this is child message....'
}
}
})
然后我们可以在组件上 监听这个自定义事件
<div id="app">
<component-child @my-event="eventHandler"></component-child>
</div>
在Vue实例的 methods
中定义这个函数
const vm = new Vue({
el: '#app',
data: {
childMessage: ''
},
methods: {
eventHandler(data) {
// 可以先打印一下 data 看看
console.log('data: ', data)
// 这里的 data 就是从子组件中传递出来的 数据,把他绑定到父组件中
this.childMessage = data;
}
}
})
然后我们就可以在父组件的任意位置用这个数据啦~
<div id="app">
<p>这条数据是从子组件中传递出来的: {{childMessage}}</p>
<component-child @my-event="eventHandler"></component-child>
</div>
缺点
props、$emit
这种方式有一个缺点,例如说组件的嵌套层次深,这种通信方式就会显得相当麻烦,需要一层一层的去传递,非常不利于维护。
二、直接访问跟实例、组件实例
为了解决以上残留的问题,我们可以使用
$root
直接访问根实例、使用$parent
访问父组件的实例、使用ref
访问子组件的实例. 我们还是用上面的组件案例来演示。
$root
的用法
修改 Vue 实例 父组件
const vm = new Vue({
el: '#app',
data: {
username: 'xiaosai'
}
})
修改 子组件
,在子组件中可以直接通过 $root
访问跟实例的数据, 不管嵌套多少层.都是访问的 Vue 实例的数据
Vue.component("component-child", {
template: `
<p>这里是子组件中,调用跟实例的数据 {{$root.username}}</p>
`,
})
修改 html 为最原始的样子
<div id="app">
<component-child></component-child>
</div>
运行的结果,可以顺利拿到 跟实例的数据
$parent
的用法
当然在这个例子中,跟实例就是 Vue 实例,也是一个父组件.
我们可以稍微修改一下代码,把$root
修改为 $parent
,运行结果在这个例子里是一样的。
Vue.component("component-child", {
template: `
<p>这里是子组件中,调用跟实例的数据 {{$parent.username}}</p>
`,
})
由此可以看出 $root
是访问跟实例的,而 $parent
只能访问父组件(实例)。
访问子组件实例
在 Vue 中,我们也可以通过给组件设置
ref
属性,通过$refs
访问子组件的实例,来看一哈我是怎么操作的吧~
修改子组件中的代码
Vue.component("component-child", {
template: `
<p>这里是子组件</p>
`,
data() {
return {
childMessage: 'this is child component data...'
}
}
})
需要在组件上定义一个 ref 属性,可以根据自己的喜欢任意命名
<div id="app">
<component-child ref="childData"></component-child>
</div>
在父组件中可以通过$refs.childData
获取子组件实例
const vm = new Vue({
el: '#app',
mounted() {
// 注意: $refs.childData 获取的是 子组件的实例,而不是data中的数据,还需要 .childMessage 才可以拿到数据
console.log(this.$refs.childData.childMessage);
}
})
注意:通过 $root
、 $parent
、$refs
访问的是实例本身,而 !不! 是! data中的数据,我们可以获取实例里面有的任何属性,例如 data
、method
computed
等等
缺点
实际上直接访问实例的这种方式也是有缺点的,就是在子组件中可以修改父组件中的数据,这是很致命的,如果遇到什么 bug,很难找出问题所在。所以 Vue 官方也是不推荐使用这种方式进行通讯,当然对于比较小型的应用来说这种方式确实是非常简单方便的。
三、可以通过依赖注入的方式进行通信
在上述第二种方式中,我们虽然可以访问到父组件的内容,但是如果嵌套的层次过多,例如有10个嵌套,我总不能 通过 $parent 一层一层的的获取把,这是很不现实的。我们需要一种方式,来解决这个问题,这就是依赖注入。
在 实例中,有两个新的选项 provide
和 inject
, 现在我们去修改一下父组件代码
const vm = new Vue({
el: '#app',
provide() {
return {
msg: '这是需要通讯的数据'
}
}
})
provide
是一个函数,它返回一个数据对象,在 子组件
中我们需要像定义 props
一样定义一个 inject
,也是一个数组,需要注意的是, inject 中的内容需要和 provide
中的 key
一致.
Vue.component("component-child", {
inject: ['msg'],
template: `
<p>子组件:{{msg}}</p>
`,
})
运行结果
缺点
使用 provide inject 这种方式通信的数据不是响应式的!!! 不是响应式的!!!
四、使用事件总线 EventBus 进行通信
可以使用一个
空的 Vue 实例
作为一个中间者,向组件来监听和派发事件,从而达到 数据通信的效果!
先创建一个空的 Vue 实例
const eventBus = new Vue()
在 #app 中添加一个按钮,并且绑定一个事件
<div id="app">
<component-child></component-child>
<button @click="clickHandler">clickMe</button>
</div>
在父组件中定义 clickHandler
方法
const vm = new Vue({
el: '#app',
data: {
username: 'xiaosai'
},
methods: {
clickHandler() {
// 调用 eventBus 作为一个中间者去抛发一个事件 myEvent ,并且携带 username 作为数据传递过去
eventBus.$emit('myEvent', this.username)
}
}
})
在子组件mounted
生命周期钩子函数中监听事件
Vue.component("component-child", {
template: `
<p>子组件:{{msg}}</p> <!-- 使用数据 -->
`,
data() {
return {
msg: ''
}
},
mounted() {
// 通过 eventBus 的 $on 函数监听事件,并且拿到数据
eventBus.$on('myEvent', data => {
// 把取到的数据赋值给 data 中
this.msg = data;
})
}
})
点击按钮查看运行结果
缺点
操作的数据依旧不是
响应式的!!!
结尾总结
除了上述几种方式以外,还可以通过 Vuex 来管理数据,对于某些应用来说 Vue 官方也推荐使用这种方式,Vuex 也不是没有缺点的,后续会出一篇文章单独总结一下Vuex。但是也不是说上面几种方法都用不到,
根据实际的业务场景去使用对应的方式才是王道!