Vue——组件间数据访问方式与通信方式的一点总结思考
任何事物都有其两面性,以下很多API的优点在某些场景下同时也是缺点。
目录
- 常态数据访问方式
- props
- 适用场景
- 优点
- 缺点
- Demo
- $attrs
- 适用场景
- 优点
- 缺点
- Demo
- provide/inject
- 适用场景
- 优点
- 缺点
- Demo
- $refs
- 适用场景
- 优点
- 缺点
- 个人不推荐使用refs
- $parent, $children
- $parent
- $children
- 适用场景
- 优点
- 缺点
- 事件通信
- v-on/$emit
- 适用场景
- 优点
- 缺点
- v-on/$listener
- 适用场景
- 优点
- 缺点
- Demo
- EventBus
- 适用场景
- 优点
- 缺点
- Demo
常态数据访问方式
props
将数据以变量参数形式传递给子组件中定义的props
官方文档解释
适用场景
父组件将数据向子组件同步
优点
- 父组件数据变化会向下传递,子组件中的数据也会跟随变化
- 复杂数据类型的内部数据变化也可以向下传递
缺点
- 子组件中直接修改数据会报错,需要使用
sync
修饰符以及$emit("update$propName$")
- 如果组件之间隔代过多会导致中间每一层组件都需要定义props
Demo
<Son :age="age">
export default {
props: {
age: {
type: Number
}
}
}
$attrs
使用v-bind将未在props中声明的参数数据向下传递
官方文档限定类型与特性
适用场景
父组件数据向子组件传递,爷爷组件向孙子组件传递,直系组件更多层的嵌套传递
优点
- 无视组件嵌套层级
- 有效减少多层组件嵌套场景里中间层组件的
props
数量
缺点
- 数据只能用于访问取值,无法修改源数据。
this.$attrs.age = 100;
console.log(this.$attrs.age) // 100
console.log(this.age); // 65
在子组件中直接对this.$attrs
下的age
属性做赋值操作会改变this.$attrs.age
的值,但是作为数据来源的祖辈组件中的源数据不受影响
Demo
多层嵌套时,中间层的每层组件都需要加上v-bind="$attrs"
<Parent :gender="gender" :age="age" v-bind="$attrs"/>
provide/inject
依赖注入时使用,即在祖先组件中,加个数据声明为依赖,放入provide
中,在其后代组件里使用inject
注入依赖,以访问祖先组件内的数据
官方文档给出的类型限制
适用场景
组件嵌套多层时向下传递数据的场景
优点
- 无视组件嵌套层级
- 若依赖声明时为js的基本类型数据,则注入的依赖数据是静态的,声明的祖辈组件内修改或注入的子代组件内修改是互不干扰的
缺点
- 如果需要使祖辈组件的数据变化动态向下传递,及响应式数据,需要在声明依赖时使用函数,子代组件注入依赖时也需要使用
computed
或者watch
来获取响应变化的数据同时子代组件的修改不会向上传递
Demo
参考我的另一篇博文:传送门
$refs
官方解释:
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素。
简单来说就是vue里进行dom操作时要用的。
适用场景
- 需要直接访问或修改子组件数据或方法时
- 子组件为固定个数,因为使用
v-for
的场景下,refs取出来的会是数组,当循环渲染多个子组件实例时,未必能稳定取到自己想要的实例
优点
- 简单粗暴,自由度高,想访问或修改的子组件数据都可以直接操作
- 层数不多的情况下甚至可以链式调用直接访问孙子组件的数据和方法
缺点
- 会让数据流向不明晰,出现逻辑bug或数据错误时难以定位
- 使用时有硬性要求,需要该元素在页面上渲染完成,否则容易报错,所以通常写在
nextTick
里,或使用前进行判空 $refs
也不是响应式的,因此你不应该试图用它在模板中做数据绑定
个人不推荐使用refs
虽然使用很方便,但是这种直接访问或修改子代组件数据和方法的工具,从某种程度上破坏了组件的隔离性,个人更建议所有组件的数据修改都在方法中定义接口以供其他组件通信调用。
但是,使用起来真的很方便,用过一次就会上瘾
$parent, $children
$parent
$parent
指向组件的组将的上一层Vue实例
注意:是指向包裹上一层的Vue实例,如果组件外还包裹多层组件库布局标签,例如
el-row
,el-col
。这时候,$parent
就不能指向父组件实例。
当组件上一层就是父组件时,$parent
才能指向父组件实例
当Son组件外包裹其他Vue实例时,$parent
指向的是上一层的el-col
的Vue实例。使用链式调用可以访问到父组件的实例,如下;
this.$parent.$parent.$parent
注意:直接覆盖赋值
$parent
的属性会改变源数据
$children
$children
指向组件内的所有vue子实例(不仅仅是自定义组件,也包括引用的组件库,如Element UI),数据结构始终是数组,即使只有一条数据。
可以看到,el-button也是vue实例,所以$children的数组里有2条数据。但是中间的div并不是vue实例,所以没有计入。
注意:修改
$children
里的实例属性会影响源数据
适用场景
父子组件访问数据;3层或以上会形成链式调用,不推荐
优点
- 无需在标签上做数据绑定或事件监听,可以直接访问父子组件实例
- 可以便捷的修改源数据
缺点
- 层数过多时的链式调用并不优雅
- 直接修改源数据会导致数据流向不明,当数据造成错误时难以定位
事件通信
v-on/$emit
v-on是给元素添加自定义事件监听的指令,事件可以通过$emit
触发,监听的回调函数会读取触发时传入的参数
适用场景
父子组件之间做事件触发或监听,隔代组件或更多层组件嵌套不推荐
优点
- 语法简单,便于使用,父组件监听子组件事件简单
- 携带参数便捷
缺点
- 多级组件嵌套时事件传递需逐层冒泡,中间发生数据变更时溯源不便
v-on/$listener
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用
适用场景
隔代组件进行事件监听或触发时使用
优点
- 无视组件嵌套层级,在进行复杂关系组件间的事件监听时更灵活自由,不必以冒泡的形式逐层传递
缺点
- 使用过程中可能不小心就定义了重名事件导致重写父组件事件,或者导致事件重复触发的问题
Demo
<GrandPa @testListeners="testListeners" />
<!-- 隔了多代组件 -->
<Son v-on="$listeners" />
<script>
this.$emit("testListeners") // son组件中可直接使用$emit触发事件
</script>
EventBus
使用事件总线来管理添加所有的监听事件与接收事件触发消息
适用场景
多层级,多同级,复杂族谱关系的组件之间互相通信并监听事件的情况
优点
- 无视组件族谱关系,使用独立的Vue实例做事件的注册和消息收发管理
- 便于溯源管理
缺点
- 某个组件发送的事件触发消息所有注册事件的组件都会收到,所以不允许出现重名事件,否则会出现错乱导致难以溯源
Demo
- 使用单独js文件注册空的Vue实例作为事件总线
// event-bus.js import Vue from 'vue' export const EventBus = new Vue();
- 在需要注册监听的组件中引入eventbus
// component1.vue import {EventBus} from "event-bus.js"; // 通常在moounted钩子中使用 EventBus.$on("demo", (params)=>{ this.demo(params)});
- 在需要触发事件的组件中引入eventbus
// component2.vue import {EventBus} from "event-bus.js"; EventBus.$emit("demo", params);