Vue 组件传参的八种方式总结


一、props 传参

子组件定义 props 有三种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 第一种数组方式
props: [xxx, xxx, xxx]

// 第二种对象方式
props: { xxx: Number, xxx: String}

// 第三种对象嵌套对象方式
props: {
xxx: {
//类型不匹配会警告
type: Number,
default: 0,
required: true,
// 返回值不是 true,会警告
validator(val) { return val === 10}
}
}

第三种对象默认支持 4 种属性,并且都是非必填的。可以随意使用

父组件传参的俩种方式

第一种静态属性传参

注意:

在不定义 props 类型的情况下 props 接受到的均为 String。
当 props 属性指定为 Boolean时,并且只有属性 key 没有值 value 时接受到的是 true

1
2
3
4
5
<!--props 接受到的均为 String -->
<children xxx="123"></children>

<!-- 有只有属性没有值, 这种情况 props 指定类型是 Boolean 则接收到的是 true -->
<children xxx></children>
第二种动态属性传参

注意:

需要区分非简写形式传入的值是对象,则会对应 props 中多个值
会保留传入值的类型
如果是表达式则获取到的是表达式的计算结果

1
2
3
4
5
6
7
8
<!-- prop 接收到 Number 类型的 123-->
<children :xxx="123"></children>

<!-- prop 接收到 Array 类型的 [1, 2, 3]-->
<children v-bind:xxx="[1, 2, 3]"></children>

<!-- prop 会接收到 xxx1 和 xxx2 俩个参数。这种不支持简写形式-->
<children v-bind="{xxx1: 1, xxx2: 2}"></children>

二、attrs 和listeners

$attrs

$attrs 会获取到 props 中未定义的属性(class 和 style 属性除外),支持响应式。常用的场景有俩种:

  1. 组件嵌套组件时可以使用 $attrs 来支持过多的属性支持。比如 elementUI 的 table 组件。支持的属性十几个,而平常封装的时候用的最多的也就一俩个。
  2. 属性默认是添加在父组件上的,有时候想把多余的属性添加在子组件上(可以结合 inheritAttrs: false 属性,让父属性不接受多余的属性)

$listeners 定义的事件都在子组件的根元素上,有时候想加到其他元素上。就可以使用 $listerners。它包含了父组件中的事件监听器(除了带有 .native 修饰符的监听器)

三、$emit 通知

Vue 默认有 $on $emit $once $off 几种方法来实现发布订阅模式,这也应用在了组件传参上。在组件上添加的特殊方法 @abc=”methods” 就相当于使用了 $on 来监听这个方法。因此组件内可以使用 $emit 来进行通知。

这里有一道考题: for 循环的时候如何拿到子组件的传值和 for 中循环的值

答案有俩种,一是 $event, 二是 闭包。只是需要注意 $event 只能获取到第一个值

1
2
3
<template v-for="item in [1, 2, 3]">
<children @abc="((val, val2) => getValue(val, item))"></children>
</template>

四、v-model

这个其实是一种通过 emit,on 的组合方式。优点再于同步值方便,写法优雅。下面三种写法其实是一个意思

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 写法 1
<children v-model="a"></children>
{
model: {
prop: 'value',
event: 'update:a',
},
methods: {
a() { this.$emit('update:a', 1)}
}
}

// 写法 2
<children :a="a" @update:a="a = $event"></children>
{
props: ['a']
methods: {
a() { this.$emit('update:a', 1)}
}
}
// 写法 3
// 1. 事件名必须是 update: + 属性名
// 2. 参数不能是表达式,最好是 data 里面的属性
<children :a.sync="a"></children>
{
props: ['a']
methods: {
a() { this.$emit('update:a', 1)}
}
}

五、插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
<!--默认插槽-->
<slot></slot>
<!--另一种默认插槽的写法-->
<slot name="default"></slot>
<!--具名插槽-->
<slot name="footer"></slot>
<!--传参插槽-->
<slot v-bind:user="user" name="header"></slot>
</div>
</template>

<!--使用-->
<children>
<!--跑到默认插槽中去-->
<div>123</div>
<!--另一种默认插槽的写法-->
<template v-slot:default></template>
<!--跑到具名插槽 footer 中去-->
<template v-slot:footer></template>
<!--缩写形式-->
<template #footer></template>
<!--获取子组件的值-->
<template v-slot:header="slot">{{slot.user}}</template>
<!--结构插槽值-->
<template v-slot:header="{user: person}">{{person}}</template>
<!--老式写法,可以写到具体的标签上面-->
<template slot="footer" slot-scope="scope"></template>
</children>

六、$refs, $root, $parent, $children

$root 获取根组件
$parent 获取父组件
$children 获取子组件(所有的子组件,不保证顺序)
$refs 组件获取组件实例,元素获取元素
七、project / inject
注意:注入的值是非响应的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--父组件 提供-->
{
project() {
return {
parent: this
}
}
}
<!--子组件 注入-->
{
// 写法一
inject: ['parent']
// 写法二
inject: { parent: 'parent' }
// 写法三
inject: {
parent: {
from: 'parent',
default: 222
}
}
}

八、Vuex

介绍

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态, 并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension, 提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

优缺点

优点

解决了多层组件之间繁琐的事件传播。
解决了多组件依赖统同一状态的问题。
单向数据流
为Vue量身定做,学习成本不高

缺点

不能做数据持久化,刷新页面就要重制,要做数据持久化可以考虑使用localstorage。
增加额外的代码体积,简单的业务场景不建议使用。

总结

Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。

  1. props / $emit 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

  1. ref 与 $parent / $children 适用 父子组件通信

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
$parent / $children:访问父 / 子实例
3. EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

  1. $attrs/$listeners 适用于 隔代组件通信

$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind=”$attrs” 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件
5. provide / inject 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

  1. Vuex 适用于 父子、隔代、兄弟组件通信

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

参考文章

Vue 组件传参的八种方式总结
几种常见的Vue组件间的传参方式