Vue3 组件之间的通信

组件之间的通信

经过前面几章的阅读,相信开发者已经可以搭建一个基础的 Vue 3 项目了!

但实际业务开发过程中,还会遇到一些组件之间的通信问题,父子组件通信、兄弟组件通信、爷孙组件通信,还有一些全局通信的场景。

TIP

这一章节的内容,Vue 3 对比 Vue 2 的变化都比较大!

这一章就按使用场景来划分对应的章节吧,在什么场景下遇到问题,也方便快速找到对应的处理办法。
父子组件通信
爷孙组件通信
兄弟组件通信
全局组件通信

父子组件通信
父子组件通信是指,B 组件引入到 A 组件里渲染,此时 A 是 B 的父级;B 组件的一些数据需要从 A 组件拿,B 组件有时也要告知 A 组件一些数据变化情况。

他们之间的关系如下, Child.vue 是直接挂载在 Father.vue 下面:

# 父组件
Father.vue
│ # 子组件
└─Child.vue

常用的方法有:
在这里插入图片描述
为了方便阅读,下面的父组件统一叫 Father.vue ,子组件统一叫 Child.vue 。

WARNING

在 Vue 2 ,有的开发者可能喜欢用 $attrs / $listeners 来进行通信,但该方案在 Vue 3 已经移除了,详见 移除 $listeners 。

props / emits
这是 Vue 跨组件通信最常用,也是基础的一个方案,它的通信过程是:

1.父组件 Father.vue 通过 props 向子组件 Child.vue 传值
2.子组件 Child.vue 则可以通过 emits 向父组件 Father.vue 发起事件通知

最常见的场景就是统一在父组件发起 AJAX 请求,拿到数据后,再根据子组件的渲染需要传递不同的 props 给不同的子组件使用。

下发 props
注:这一小节的步骤是在 Father.vue 里操作。

下发的过程是在 Father.vue 里完成的,父组件在向子组件下发 props 之前,需要导入子组件并启用它作为自身的模板,然后在 setup 里处理好数据并 return 给 用。

在 Father.vue 的 script /> 里:

// Father.vue
import {
    defineComponent } from 'vue'
import Child from '@cp/Child.vue'

interface Member {
   
  id: number
  name: string
}

export default defineComponent({
   
  // 需要启用子组件作为模板
  components: {
   
    Child,
  },

  // 定义一些数据并 `return` 给 `<template />` 用
  setup() {
   
    const userInfo: Member = {
   
      id: 1,
      name: 'Petter',
    }

    // 不要忘记 `return` ,否则 `<template />` 拿不到数据
    return {
   
      userInfo,
    }
  },
})

然后在 Father.vue 的 template /> 这边拿到 return 出来的数据,把要传递的数据通过属性的方式绑定在组件标签上。

<!-- Father.vue -->
<template>
  <Child
    title="用户信息"
    :index="1"
    :uid="userInfo.id"
    :user-name="userInfo.name"
  />
</template>

这样就完成了 props 数据的下发。

在 template /> 绑定属性这里,如果是普通的字符串,比如上面的 title,则直接给属性名赋值就可以。

如果是变量名,或者其他类型如 number 、 boolean 等,比如上面的 index,则需要通过属性动态绑定的方式来添加,使用 v-bind: 或者 : 符号进行绑定。

另外官方文档推荐对 camelCase 风格(小驼峰)命名的 props ,在绑定时使用和 HTML attribute 一样的 kebab-case 风格(短横线),例如使用 user-name 代替 userName 传递,详见官网的 传递 prop 的细节 一节。

接收 props
注:这一小节的步骤是在 Child.vue 里操作。

接收的过程是在 Child.vue 里完成的,在 script /> 部分,子组件通过与 setup 同级的 props 来接收数据。

它可以是一个 string[] 数组,把要接受的变量名放到这个数组里,直接放进来作为数组的 item :

// Child.vue
export default defineComponent({
   
  props: ['title', 'index', 'userName', 'uid'],
})

但这种情况下,使用者不知道这些属性的使用限制,例如是什么类型的值、是否必传等等。

带有类型限制的 props
注:这一小节的步骤是在 Child.vue 里操作。

和 TypeScript 一样,类型限制可以为程序带来更好的健壮性, Vue 的 props 也支持增加类型限制。

相对于传递一个 string[] 类型的数组,更推荐的方式是把 props 定义为一个对象,以对象形式列出,每个 Property 的名称和值分别是各自的名称和类型,只有合法的类型才允许传入。

TIP

注意,和 TS 的类型定义不同, props 这里的类型,首字母需要大写,也就是 JavaScript 的基本类型。

支持的类型有:
在这里插入图片描述
了解了基本的类型限制用法之后,接下来给 props 加上类型限制:

// Child.vue
export default defineComponent({
   
  props: {
   
    title: String,
    index: Number,
    userName: String,
    uid: Number,
  },
})

现在如果传入不正确的类型,程序就会抛出警告信息,告知开发者必须正确传值。

如果需要对某个 Prop 允许多类型,比如这个 uid 字段,可能是数值,也可能是字符串,那么可以在类型这里,使用一个数组,把允许的类型都加进去。

// Child.vue
export default defineComponent({
   
  props: {
   
    // 单类型
    title: String,
    index: Number,
    userName: String,

    // 这里使用了多种类型
    uid: [Number, String],
  },
})

可选以及带有默认值的 props
注:这一小节的步骤是在 Child.vue 里操作。

所有 props 默认都是可选的,如果不传递具体的值,则默认值都是 undefined ,可能引起程序运行崩溃, Vue 支持对可选的 props 设置默认值,也是通过对象的形式配置 props 的选项。

其中支持配置的选项有:
在这里插入图片描述
了解了配置选项后,接下来再对 props 进行改造,将其中部分选项设置为可选,并提供默认值:

// Child.vue
export default defineComponent({
   
  props: {
   
    // 可选,并提供默认值
    title: {
   
      type: String,
      required: false,
      default: '默认标题',
    },

    // 默认可选,单类型
    index: Number,

    // 添加一些自定义校验
    userName: {
   
      type: String,

      // 在这里校验用户名必须至少 3 个字
      validator: (v) => v.length >= 3,
    },

    // 默认可选,但允许多种类型
    uid: [Number, String],
  },
})

使用 props
注:这一小节的步骤是在 Child.vue 里操作。

在 template /> 部分, Vue 3 的使用方法和 Vue 2 是一样的,比如要渲染父组件传入的 props :

<!-- Child.vue -->
<template>
  <p>标题:{
   {
    title }}</p>
  <p>索引:{
   {
    index }}</p>
  <p>用户id:{
   {
    uid }}</p>
  <p>用户名:{
   {
    userName }}</p>
</template>

但是 script /> 部分,变化非常大!

在 Vue 2 里,只需要通过 this.uid 、 this.userName 就可以使用父组件传下来的 Prop ,但是 Vue 3 没有了 this ,所以是通过 setup 的入参进行操作。

// Child.vue
export default defineComponent({
   
  props: {
   
    title: String,
    index: Number,
    userName: String,
    uid: Number,
  },

  // 在这里需要添加一个入参
  setup(props) {
   
    // 该入参包含了当前组件定义的所有 props
    console.log(props)
  },
})

关于 Setup 函数的第一个入参 props :

1.该入参包含了当前组件定义的所有 props (如果父组件 Father.vue 传进来的数据在 Child.vue 里未定义,不仅不会拿到,并且在控制台会有警告信息)。
2.该入参可以随意命名,比如可以写成一个下划线 _ ,通过 _.uid 也可以拿到数据,但是语义化命名是一个良好的编程习惯。
3.该入参具备响应性,父组件修改了传递下来的值,子组件也会同步得到更新,因此请不要直接解构,可以通过 toRef 或 toRefs API 转换为响应式变量

传递非 props 的属性
上一小节最后有一句提示是:
如果父组件 Father.vue 传进来的数据在 Child.vue 里未定义,不仅不会拿到,并且在控制台会有警告信息。

这种情况虽然无法从 props 里拿到对应的数据,但也不意味着不能传递任何未定义的属性数据,在父组件,除了可以给子组件绑定 props ,还可以根据实际需要去绑定一些特殊的属性。

比如给子组件设置 class、id,或者 data-xxx 之类的一些自定义属性,如果子组件 Child.vue 的 template /> 里只有一个根节点,那么这些属性默认会自动继承并渲染在 Node 节点上。

假设当前在子组件 Child.vue 是如下这样只有一个根节点,并且未接收任何 props :

TIP

如果已安装 Vue VSCode Snippets 这个 VSCode 插件,可以在空的 .vue 文件里输入 v3 ,在出现的代码片段菜单里选择 vbase-3-ts 生成一个 Vue 组件的基础代码片段。
<!-- Child.vue -->
<template>
  <div class="child">子组件</div>
</template>

<script lang="ts">
import {
    defineComponent } from 'vue'

export default defineComponent({
   
  setup() {
   
    return {
   }
  },
})
</script>

<style scoped>
.child {
   
  width: 100%;
}
</style>

在 Father.vue 里对 Child.vue 传递了多个属性:

<!-- Father.vue -->
<template>
  <Child
    id="child-component"
    class="class-name-from-father"
    :keys="['foo', 'bar']"
    :obj="{ foo: 'bar' }"
    data-hash="b10a8db164e0754105b7a99be72e3fe5"
  />
</template>

回到浏览器,通过 Chrome 的审查元素可以看到子组件 Child.vue 在渲染后,按照 HTML 属性的渲染规则生成了多个属性:

<!-- Child.vue 在浏览器里渲染后的 HTML DOM 结构 -->
<div
  class="child class-name-from-father"
  id="child-component"
  keys="foo,bar"
  obj="[object Object]"
  data-hash="b10a8db164e0754105b7a99be72e3fe5"
  data-v-2dcc19c8=""
  data-v-7eb2bc79=""
>
  子组件
</div>

TIP

其中有两个以 data-v- 开头的属性是 <style /> 标签开启了 Style Scoped 功能自动生成的 Hash 值。

可以在 Child.vue 配置 inheritAttrs 为 false 来屏蔽这些非 props 属性的渲染。

// Child.vue
export default defineComponent({
   
  inheritAttrs: false,
  setup() {
   
    // ...
  }
  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值