组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,那么如何做到组件的数据互相引用也就是组件通信呢? 下面是针对不同的使用场景,总结的组件通信方法:
父子组件通信
父组件通过props的方式向子组件传递数据,子组件向父组件传递通过子组件中$emit自定义事件,父组件v-on监听事件
父组件向子组件传递数据
父组件通过props传递数据给子组件
<template> //App.vue父组件
<div id="app">
<users v-bind:users="users"></users> //自定义名称方便子组件调用,后面传递数据名
</div>
</template>
<script>
import Users from "./components/Users"
export default {
name: 'App',
data(){
return{
users:["Jack","Tom"]
}
},
components:{
"users":Users
}
}
<template> //users子组件
<div class="hello">
<ul>
<li v-for="user in users">{{user}}</li> //遍历传递过来的值,然后展示到页面
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props:{ //外部属性,接收父组件传递过来的users
users:{
type:Array,
required:true
}
}
}
</script>
子组件向父组件传递数据
子组件通过$emit绑定自定义事件和要传递的数据:$emit('事件名','要传递的数据')
父组件通过v-on监听自定义事件,获取子组件的数据:v-on:事件名='函数' 或 @事件名='函数'
<template> // 父组件 app.vue
<div id="app"> // v-on监听子组件的titleChanged事件
<app-header v-on:titleChanged="updateTitle" ></app-header>
<h2>{{title}}</h2> // 对应父组件本身的updateTitle方法
</div>
</template>
<script>
import Header from "./components/Header"
export default {
name: 'App',
data(){
return{
title:"传递的是一个值"
}
},
methods:{
updateTitle(e){ // 更新title
this.title = e;
}
},
components:{
"app-header":Header,
}
}
</script>
<template> // 子组件 app-header
<header> // 绑定点击事件时,执行changeTitle函数
<h1 @click="changeTitle">{{title}}</h1>
</header>
</template>
<script>
export default {
name: 'app-header',
data() {
return {
title:"Vue.js Demo"
}
},
methods:{
changeTitle() { // 传递事件名titleChanged,传递值"向父组件传值"
this.$emit("titleChanged","向父组件传值");
}
}
}
</script>
任意组件通信
通过一个空的 Vue 实例作为总的事件中心,用它来触发事件和监听事件,实现了任何组件间的通信:包括父子、兄弟、跨级
var eventBus = new Vue()
eventBus.$emit(事件名,数据) // 绑定自定义事件
eventBus.$on(事件名, data=>{}) // 监听自定义事件
有3个平级组件A、B、C,C组件如何获取A或B组件的数据
eventBus.$on监听了自定义事件data-a和data-b,使用mounted在组件挂载之后执行函数
<div id="all">
<my-a></my-a>
<my-b></my-b>
<my-c></my-c>
</div>
<template id="a">
<div>
<h3>A组件:{{name}}</h3>
<button @click="send">将数据发送给C组件</button>
</div>
</template>
<template id="b">
<div>
<h3>B组件:{{age}}</h3>
<button @click="send">将数组发送给C组件</button>
</div>
</template>
<template id="c">
<div>
<h3>C组件:{{name}},{{age}}</h3>
</div>
</template>
<script>
var eventBus = new Vue(); // 定义空的Vue实例
var A = {
template: '#a',
data() {
return {
name: 'tom'
}
},
methods: {
send() {
eventBus.$emit('data-a', this.name); // 使用eventBus.$emit绑定事件
}
}
}
var B = {
template: '#b',
data() {
return {
age: 20
}
},
methods: {
send() {
eventBus.$emit('data-b', this.age);
}
}
}
var C = {
template: '#c',
data() {
return {
name: '',
age: ""
}
},
mounted() { // 在挂载组件之后执行函数
eventBus.$on('data-a',name => { // 使用eventBus.$on监听事件
this.name = name;
})
eventBus.$on('data-b',age => {
this.age = age;
})
}
}
var vm = new Vue({
el: '#all',
components: {
'my-a': A,
'my-b': B,
'my-c': C
}
});
</script>
任意组件通信(使用Vuex)
Vuex是专门为vue.js开发的状态管理工具,在全局拥有一个State存放数据,当组件要更改State中的数据时
必须使用Mutation,异步操作使用Action提交Mutation,最后根据State的变化,渲染到视图上
关于Vuex的内容点击我的另一篇文章: Vuex 入门
Vuex 配合 localStorage 使用
Vuex存储的数据是响应式的,但并不会保存,刷新之后就回到了初始状态
在Vuex的数据改变时把数据拷贝一份到localStorage里面,每次使用都从localStorage里取出保存的数据
替换store里的state,数据改变之后在从新存入到localStorage
注意事项:Vuex里,我们保存的状态都是数组,而localStorage只支持字符串,所以需要用JSON转换
JSON.stringify(); // array -> string
JSON.parse(window.localStorage.getItem()); // string -> array
let defaultCity = "上海"
try { // 防止用户关闭本地存储功能还能继续执行代码
if (!defaultCity){
defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
}
}catch(e){}
export default new Vuex.Store({
state: {
city: defaultCity
},
mutations: {
changeCity(state, city) {
state.city = city
try { // 数据改变时从新存一份到localStorage
window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
} catch (e) {}
}
}
})
任意组件通信(使用 listeners)
$attrs:包含了父作用域中不作为prop被识别的特性绑定 (class和style除外)
当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定 (class和style除外)
并且可以通过v-bind="$attrs"传入内部组件,在创建高级别的组件时非常有用
$listeners:包含了父作用域中的 (不含.native修饰器的) v-on事件监听器
它可以通过v-on="$listeners"传入内部组件——在创建更高层次的组件时非常有用
Vue.component('C',{
template:`
<div>
<input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)">
</div>
`,
methods:{
passCData(val){ // 触发父组件A中的事件
this.$emit('getCData',val)
}
}
})
Vue.component('B',{
data(){
return {
mymessage:this.message
}
},
template:`
<div> // C组件能直接触发getCData,B组件调用C组件时,使用v-on绑定$listeners 属性
<input type="text" v-model="mymessage" @input="passData(mymessage)">
<C v-bind="$attrs" v-on="$listeners"></C>
</div> // 使用v-bind绑定$attrs属性,C可以获取到A传的props,不能获取B中props
`,
props:['message'], // 得到父组件传递过来的数据
methods:{
passData(val){ // 触发父组件中的事件
this.$emit('getChildData',val)
}
}
})
Vue.component('A',{
template:`
<div>
<p>this is parent compoent!</p>
<B :messagec="messagec" :message="message" v-on:getCData="getCData"
v-on:getChildData="getChildData(message)"></B>
</div>
`,
data(){
return {
message:'hello',
messagec:'hello c' // 传递给c组件的数据
}
},
methods:{
getChildData(val){
console.log('这是来自B组件的数据')
},
getCData(val){ // 执行C子组件触发的事件
console.log("这是来自C组件的数据:"+val)
}
}
})
父组件向下传递数据(provide和inject)
provide(提供)和inject(注入)需要一起使用
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效
祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量,解决跨级组件间的通信问题
主要使用场景是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系
Vue.component('parent',{
template:`
<div>
<p>this is parent compoent!</p>
<child></child>
</div>
`,
provide:{
for:'test' // 提供传递给子组件的数据
},
data(){
return {
message:'hello'
}
}
})
Vue.component('child',{
inject:['for'], // 得到父组件传递过来的数据
data(){
return {
mymessage:this.for
}
},
template:`
<div>
<input type="tet" v-model="mymessage">
</div>
})
得到组件实例,调用组件的方法访问数据 (children与 ref)
ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例
$parent 访问父实例
$children 访问子实例
export default { // component-a 子组件
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
<template> // 父组件
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
comA.sayHello();
}
}
</script>
总结
父子通信:父向子props,子向父$emit;$parent/$children;provide/inject API;$attrs/$listeners
兄弟通信:eventBus;Vuex
跨级通信:eventBus;Vuex;provide/inject API;$attrs/$listeners