对于Vue来说组件的数据通信非常重要,面试中也是频繁出现,为了更加深入了解组件的数据通信,本文专门总结了一下组件之间通信的场景和通信方式如何实现。
组件通信大致有以下场景:
接下来本文将介绍以下通信方式
props/emit
传参是最基础的组件通信方式,父组件通过props
可以向子组件进行通信,子组件通过emit
向父组件进行通信。
通过在子组件中定义 props
参数,父组件传入子组件中定义的参数属性来实现通信。
//父组件
<template>
< msg :word="word"></ msg>
</template>
<script>
import Msg from './Msg.vue'
export default {
components: {
msg
},
data() {
return {
word: '该做核酸了'
}
}
}
</script>
//子组件
<template>
<div>
<span>{{word}}</span>
</div>
</template>
<script>
export default {
props: {
word: {
type: String,
default: 'test'
}
}
}
</script>
Vue 通过 on/emit
的方式来实现子组件向父组件传参,在子组件中使用 $emit
绑定一个自定义事件,当执行语句时,就会将参数传递给父组件;父组件通过$on 来监听子组件自定义的事件获取子组件传递的参数。
//父组件
<template>
<msg :num="num" @add='add'></ msg>
</template>
<script>
import Msg from './Msg.vue'
export default {
components: {
msg
},
data() {
return {
num:10
}
},
methods:{
add(res){
this.num = res
}
}
}
</script>
//子组件
<template>
<div>
<button ></button>
</div>
</template>
<script>
export default {
props: {
num: {
type: Number,
default: 100
}
},
methods:{
add(){
this.$emit('add',this.num + 1)
}
}
}
</script>
最常见的父子组件通信方式
props支持参数验证
emit 只会触发父组件的监听事件
不适合多层次组件参数传递,需要逐层传递参数
通过 $patent / $children 可以拿到父子组件的实例 ,从而调用实例里的数据方法,实现父子组件通信
事件bus :eventBus
,相当于所有组件共用一个事件中心,这个事件中心用来管理事件,当我们需要的时候就向事件中心发送或者接收事件。通过共享一个vue实例,使用该实例的 $on
以及 $emit
实现数据传递。
跨层级访问数据
任意组件只要导入 bus
(bus.js) 就可以随意,发送 与监听数据
//导入vue
import Vue from "vue";
// 导出vue 创建的空实例
var bus = new Vue();
export default bus;
在要发送数据的组件中导入bus.js ,并使用bus.$emit
<template>
<div id="app">
</div>
</template>
<script>
import bus from "@/utils/bus";
export default {
//App.vue 作为数据的提供方
provide: {
a: "明天要放假了", // 只要子孙元素都可以接收
},
name: "app",
components: { TabsCom },
data() {
return {
msg: "来自app",
};
},
//在mounted 里面发送
mounted() {
setTimeout(() => {
this.changeIt();
}, 5000);
},
//在任意组件中都可以发送和接收
methods: {
changeIt() {
this.msg = "下楼做核酸了 ";
bus.$emit("msgchange", this.msg);
},
},
};
</script>
在有接收数据的组件中导入 bus.js
,并使用bus.$on
(注意this)
<template>
<div class="nav" :style="{ backgroundColor: bg_color, color: text_color }">
<div class="left" @click="$emit('left-click', $event)">
<slot name="icon_left"></slot> {{ left_text }}
</div>
<div class="title">
<slot name="title"></slot>
{{ title }}
</div>
<div class="right" @click="$emit('right-click', $event)">
<slot name="icon_right"></slot> {{ right_text }}
</div>
</div>
</template>
<script>
import bus from "@/utils/bus";
export default {
//接收的地方要监听数据,$event 就是app页面发送的数据
created() {
bus.$on("msgchange", ($event) => {
console.log("msgchange");
this.myMsg = $event;
});
},
data() {
return {
myColor: "#f30",
myMsg: "",
};
},
props: {
text_color: {
type: String,
default: "#000",
},
bg_color: {
type: String,
default: "#fff",
},
title: {
type: String,
default: "",
},
left_text: {
type: String,
default: "返回",
},
right_text: {
type: String,
default: "",
},
},
};
</script>
// 组件销毁时需要解绑监听
beforeDestroy () {
bus.$off('myMsg')
}
- 常用于多层嵌套组件场景下兄弟组件或任意两个组件之间通讯。
$on
事件 是不会自动清除销毁的,需要手动销毁,可以在beforeDestroy
中解绑监听,避免重复触发。- 适合简单场景下使用,太过复杂的场景建议使用
vuex
。
provide
提供数据,所有子孙都可以通过 inject
注入数据,inject
接收父辈组件提供的数据。provide
和inject
依赖注入 ,跨层级访问(只读)
- 跨层级组件之间通信
- 传递的属性是非响应的
- 如果需要传递响应属性,采用函数的方式传入对象
- 复杂组件不建议使用此方式传参,任意层级都能访问导致数据追踪比较困难
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态, 并以相应的规则保证状态以一种可预测的方式发生变化。
简单来说,Vuex 在大型,特大型,vue项目中做数据统一管理的;在vuex中存储的数据,每个组件都可以引用到,vuex中的数据发生变化,引用该数据的组件会自动更新。
Vuex包含以下几个部分:全局唯一的状态管理仓库(state),同步操作(mutations)、异步操作(actions)、缓存依赖(getters),模块(modules)。
具体使用方法访问官网查看 ==> Vuex官网
$store.dispatch('login',data)
方法就可以//定义vuex的数据地方
state:{
cartNum:10
}
//在组件访问数据
$store.state.cartNum
mutations: {
//修改state数据必须在mutations中的方法
//方法名建议大写
SET_CART_NUM(state, data) {
//修改cartNum的值
state.cartNum = data;
}
},
//在组件中访问mutations的方法
$store.commit('SET_CART_NUM',100)
actions: {
//定义异步,网络延迟等方法
//只能调用mutations,不能直接修改state
getCartNum(context,data){
//可以执行网络请求,等待延迟
setTimeout(()=>{
//等待4秒后执行mutations 的SET_CART_NUM方法
context.commit('SET_CART_NUM',data)
},4000)
}
},
//在组件中调用
$store.dispatch('getCartNum',33)
getters: {
//从现有数据计算新的数据,每个商品佣金是0.5元
//fee 佣金会随着cartNum变化而变化
fee:function(state){
return state.cartNum*0.5
}
},
//在组件中调用
$store.getters.fee
module 把vuex的数据分成多个子模块(添加子组件)
import { Login, Reg } from "@/api/user";
import $router from "@/router/index";
//导入vue
import Vue from "vue";
//导入插件
import Notify from "@/plugin/Notify";
//使用插件
Vue.use(Notify);
export default {
state: {
//用户信息
userInfo: {
name: "",
score: 0,
},
//token 标识
token: "",
},
mutations: {
//负责修改用户信息
SET_USERINFO(state, data) {
//更新state 的userInfo
state.userInfo = data;
//本地存储用户信息
localStorage.setItem("user", JSON.stringify(data));
},
//修改token
SET_TOKEN(state, data) {
state.token = data;
//token 本来就是字符串,不需要
localStorage.setItem("token", data);
},
},
actions: {
//页面退出
logout(context) {
context.commit("SET_USERINFO", {});
context.commit("SET_TOKEN", "");
},
//负责登录 (登录是异步的需要放在actions中)
login(context, data) {
Login(data)
.then((res) => {
//200 代表成功其他代表失败
if (res.data.code === 200) {
Notify.success(res.data.msg || "登录成功");
//登录成功弹出设置用户信息与设置token
context.commit("SET_USERINFO", res.data.user);
context.commit("SET_TOKEN", res.data.token);
//跳转到redirect 对应页面
//获取当前留言信息
var $route = $router.history.current;
//获取查询参数
var redirect = $route.query.redirect || "/";
//实现跳转
$router.replace(redirect);
} else {
//登录不成功清空用户信息
Notify.danger(res.data.msg || "登录失败");
context.commit("SET_USERINFO", {});
context.commit("SET_TOKEN", "");
}
})
//失败显示网络失败
.catch((err) => {
console.log(err);
Notify.danger("网络失败");
});
},
};
<template>
<div>
<h1>我的h1>
<p v-if="$store.state.user.userInfo.name">
<label>
<input type="file" name="file" ref="myfile" @change="fileChange()" />
<img :src="user.avatar" width="100" class="avatar" />
label>
{{ $store.state.user.userInfo.name }},积分:{{
$store.state.user.userInfo.score
}}
<a href="" @click.prevent="$store.dispatch('logout')">退出a>
p>
<p v-else>
<router-link to="/login?redirect=/user">登录router-link> <br />
<router-link to="/reg">注册router-link>
p>
div>
template>
- 任意组件之间通信,多组件之间通信
- 跨路由组件之间通信
- 刷新浏览器,vuex数据会丢失,可以采用vuex-persistedstate插件解决此问题
- 适合场景复杂的大型项目,简单的业务场景不建议使用
【Vue】Vue2生命周期详解
【Vue】axios的二次封装和使用(附详细代码)
【Vue】vue组件和vue插件的创建和使用(底部栏组件、Toast 和 Notify通知插件)
【Vue】 vue2路由搭建和搭建vue2脚手架(入门级)