Vue进阶——组件之间的通信
在vue中组件是可复用的Vue实例,本质上是一个对象。而随着项目的不断增大,总是会有很多重复的模块,而把这些重复的模块提取出来封装成共用的组件。这无疑是大大的减少代码量与页面逻辑。当封装成组件时,如何给组件内变量赋值取值,这又成为了新的问题。这时候就需要了解几种组件之间的通信,更好的实现组件之间的信息的传递。
一、使用props属性
:适用于父子组件之间的通信
在父组件中调用子组件,在组件中以自定义属性的方式给子组件传值,子组件中使用props接收父组件的值;
<template name="父组件">
<div>
<!-- 使用自定义事件的方式传递给子组件监听事件 -->
<PageA :contA="contA" :textA="textA" @changeText="changeText" :objA="objA" />
<Button type="success" @click="changeText">父组件</Button>
</div>
</template>
<script>
import PageA from "@/components/pageA";
export default {
components: {
PageA
},
name: 'home',
data() {
return {
contA: '父组件contA',
textA: 5555,
objA: {name: "哈哈"}
}
},
methods: {
// 父组件改变textA的值
changeText(){
this.textA++;
}
}
}
</script>
<template name="子组件">
<div>
<h2>{{contA}}--{{textA}}</h2>
<Button type="success" @click="changeText">textA++</Button>
<div @click="objChange">{{objA.name}}</div>
</div>
</template>
<script>
export default {
name: 'pageA',
// 第一种props以数组的方式接收
// props: ['contA','textA'],
// 第二种props以对象形式接收可以给默认值
props: {
contA: {
type: String,
required: false, // 是否必须
default: '默认内容'
},
status: {
type: String,
required: true,
validator: function (value) {
return [
'error',
'success',
'fail',
].indexOf(value) !== -1
} // 验证传递过来的是否是数组其中一项
},
textA: {
type: Number,
required: true,
default: 77777
},
objA: {
type: Object,
default: {}
}
},
methods: {
changeText(){
// 第一种改变父组件值:通知父组件改变变量
this.$emit('changeText');
},
objChange(){
// 第二种改变父组件的值:此时可以改变父组件里面的值,因为父组件将objA的内存地址传递过来了,父子组件同用一个objA的内存地址,所以这里改变父组件的值。(请根据需求谨慎使用)
this.objA.name = "张三";
}
}
}
</script>
props属性总结:
- 父组件通过自定义属性方式传值给子组件,子组件使用props接收;
- props接收可以数组或对象的形式;
- 父组件传递的值是基本类型时,子组件不能直接改变父组件的值,需要使用自定义方法通知父组件更改;
- 父组件传递的值是引用类型时,子组件可以直接改变父组件的值,因为此时父子共用一个内存地址;
二、创建Vue实例作为事件中心
,
e
m
i
t
/
emit/
emit/on事件监听器: 适用兄弟之间传值,稍微复杂一些情况建议使用下面的vuex;
通过创建一个空的Vue实例作为事件中心,const EventVue = new Vue()
, 发送方利用Event.
e
m
i
t
(
′
事
件
名
称
′
,
传
递
的
值
)
,
接
收
方
利
用
E
v
e
n
t
.
emit('事件名称',传递的值),接收方利用Event.
emit(′事件名称′,传递的值),接收方利用Event.on(‘事件名称’,事件处理函数); 注意:接收事件要放到created或之前触发监听;
<template name="兄弟A">
<div>
</div>
</template>
<script>
import Event from "@/utils/vm";
export default {
name: 'pageA',
data() {
return {
name: "a"
}
},
methods: {
send(){
Event.$emit('global',this.name);
}
}
}
</script>
<template name="兄弟B">
<div>
<h2>{{name}}</h2>
</div>
</template>
<script>
export default {
name: 'pageB',
data(){
return {
name: "888"
}
},
created(){
Event.$on('global',name=>{
this.name = name;
});
},
beforeDestroy(){
// 关闭事件监听器
Event.$off('global');
}
}
</script>
e m i t / emit/ emit/on事件监听器总结:
- 首先必须要创建一个空的vue实例,通信的两个组件必须事件名一样。不同的组件通信事件名不能相同;
- 传递的数据属于单向数据流,并不是响应式的;
- 出于性能考虑可以在组件卸载前关闭事件监听器;
- 假如只需要监听一次事件时,可以使用
Event.$once('事件名',处理函数)
;
三、vuex状态管理
:适用于大型项目中的数据存储,从而实现页面中的通信;
这里直接是使用vuex的方法,详细的vuex在之前笔记中,vue进阶——vuex状态管理。下面单纯的介绍一下用法:将多个页面需要的数据封装到一个 store 子仓库中,当需要的时候在仓库中拿去就好,利用 mutations 同步改变值;
// vuex仓库代码
<script>
// 这里是子仓库,记得要在主仓库中的modules中挂载
export default {
namespaced: true,
state() {
return {
number: 22
}
},
// 同步
mutations: {
SET_REDUCE(state) {
state.number--
},
SET_ADD(state) {
state.number++
}
}
}
</script>
<template>
<div>
<h2>vuex状态管理通信</h2>
<div>组件A</div>
<BaseStep class="base-step" :data="number" @reduce="SET_REDUCE" @add="SET_ADD" />
</div>
</template>
<script>
import { mapMutations, mapState } from "vuex";
export default {
// 利用计算器属性拿到store仓库数据
computed: {
...mapState({
number: state => state.Msg.number
})
},
methods: {
// 辅助方法映射出同步方法
...mapMutations("Msg", ["SET_REDUCE", "SET_ADD"]),
}
};
</script>
// 组件B与上面一样把仓库先导出进组件,可以使用组件同步方法与数据
vuex状态管理通信总结:
- 这种情况适合大型项目,嵌套层级比较复杂的页面进行通信;
- 首先要在需要在store仓库中创建变量,在需要的组件中导入使用,同步方法看情况导入;
- 需要注意:在web页面中页面刷新后会使store变为初始值,利用本地存储保持状态的保持
四、ref、$parent和$children
: 适用于父子组件之间通信
ref表示一个对象,持有注册过 ref
attribute 的所有 DOM 元素和组件实例。
p
a
r
e
n
t
与
parent与
parent与children都属于vue实例方法;获取对应的父组件或子组件的实例和方法。
// 获取组件的父组件的组件实例,同时可以使用父组件的变量与方法
console.log(this.$parent);
// 获取组件的所有子组件的实例,返回的子组件实例数组利用唯一值_uid区分,也可以使用子组件的变量与方法
console.log(this.$children);
// 假设在组件A中引用的组件B,给组件B的标签中ref名,就可以获取组件B的实例
console.log(this.$refs.pageB);
总结:
- 获取$children时,在父组件的created生命周期之后获取,因为此时子组件还未完全渲染完,无法获取到所有子组件实例;具体与父子组件生命周期有关;
- 如果组件没有父组件时,$parent返回的组件实例就为本组件实例;
五、provide/inject
:适用层级嵌套比较深的组件之间通信
在vue2.2.0中新增的方法,祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。跨级组件之间建立了一种联系;
// 祖先组件中
data() {
return {
objProvide: {
id: 333
}
};
},
provide() {
return {
// 这里之所以绑定一个对象,主要是将内存地址传递给祖孙组件,这样数据就可以变成响应式
objProvide: this.objProvide
};
},
// 祖孙组件中接收值的方式可以效仿props
// inject: ["objProvide"],
inject: {
objProvide: {
type: Object,
default: {}
}
},
总结:
- this._provided 指向当前vue实例的provide对象,可以在祖先组件方法中再次更改
- 官方文档中说provide与inject绑定不是响应式的,但是传入可监听的对象时就可以实现响应式;
- 此方法用于高阶组件,无论多少层都会被子组件拿到;
- provide 选项应该是一个对象或返回一个对象的函数,inject 选项应该是:一个字符串数组,或 一个对象
六、$attrs/$listeners
: 适用于多级嵌套的组件之间传递值
这对方法是vue2.4新增的,假设A组件传给子组件B自定义属性时,B组件没有使用props接收,便可以使用this.$attrs
拿到剩余属性值;在B组件中有C组件,C组件中可以使用this.
a
t
t
r
s
拿
到
A
组
件
的
值
。
前
提
是
B
组
件
中
引
用
C
组
件
时
导
入
了
‘
attrs拿到A组件的值。前提是B组件中引用C组件时导入了`
attrs拿到A组件的值。前提是B组件中引用C组件时导入了‘attrs`对象;
// 以下组件的引入与注册省略了,请注意
// A组件
<template>
<B :obj1="obj1" @cFn="cFn" />
</template>
<script>
data(){
return {
obj1: {id: 2}
}
},
methods: {
cFn(){
console.log("C组件中调用方法")
}
}
</script>
// B组件
<template>
<C v-bind="$attrs" v-on="$listeners" />
</template>
<script>
mouthed(){
console.log(this.$attrs,"可以获取A组件中的obj1对象");
}
</script>
// C组件
<template>
<h1 @click="cFn">点击改变A组件的obj1</h1>
</template>
<script>
mouthed(){
console.log(this.$attrs,"可以获取A组件中的obj1对象");
},
methods: {
cFn(){
// 触发A组件的方法
this.$emit('cFn');
// 这里也可以直接改变A组件的值,因为传递过来的是一个对象
this.$attrs.obj1.id = 2222;
}
}
</script>
方法总结:
- 首先在最外层把需要传递的值传给下层组件,只能监听未被prop获取到的,如果没有就是空对象;
- 利用
v-bind="$attrs"与v-on="$lienters"
就未被监听的数组传递给祖孙后代组件; - 如果传递值是一个对象可以直接改变里面的值,如果是基本类型就需要使用的自定义事件通知祖先组件进行更改;
- Vue2.4还新增了
inheritAttrs
属性,Boolean类型,默认值为true。如果是不想
组件有继承特性,就可以设置为false,这样子代$attrs就拿不到值;
七、本地缓存进行组件之间的通信
:无法做到数据的响应式
有时候我们在很多地方都会需要常量,这些都是不经常变化的,例如登录信息等。每次调用接口获取就比较麻烦,这时就可以采用缓存进行存储,组件中需要时直接在缓存里面获取就好。注意:敏感信息最好不要存储到浏览器上,不安全。
各服务端缓存大小限制:
- H5端为localStorage,浏览器限制5M大小,可以设置过期时间,sessionStorage存在回话期,浏览器关闭就清除;
- pp端为原生的plus.storage,无大小限制,不是缓存,是持久化的。
- 各个小程序端为其自带的storage api,数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。
- 微信小程序单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
- 支付宝小程序单条数据转换成字符串后,字符串长度最大200*1024。同一个支付宝用户,同一个小程序缓存总上限为10MB。(百度、字节小程序未知)
- 非App平台清空Storage会导致uni.getSystemInfo获取到的deviceId改变
// 原生app本地存储
plus.storage.setItem(key, value);
// uniapp本地存储
uni.setStroageSync(key,value);
// web端
localStorage.setItem(key,value);
// 小程序端
wx.setStroageSync(key,value);