存在的问题

如果我们一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿难以维护

  • 组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件
  • 再将这些组件组合嵌套在一起,最终形成我们的应用程序

按照如下的方式进行拆分

各个组件间存在如下关系:

  • App 组件是 Header、 Main、 Footer 组件的父组件
  • Main 组件是 Banner、 ProductList 组件的父组件

那么这些组件间如何互相通信?

  • 比如 App 可能使用了多个 Header,每个地方的 Header 展示的内容不同,那么我们就需要使用者传递给 Header 一些数据,让其进行展示
  • 又比如我们在 Main 中一次性请求了 Banner 数据和 ProductList 数据,那么就需要传递给它们来进行展示
  • 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件

总之,在 Vue 项目中,组件之间的通信是非常重要且常见的

父子通信-父传子

  • 父组件传递给子组件: 通过 props 属性
  • 子组件传递给父组件: 通过 $emit 触发事件

Props

  • Props 是你可以在组件上注册一些自定义的 attribute
  • 父组件给这些 attribute 赋值子组件通过 attribute 的名称获取到对应的值

常见的用法:

  • 方式一:字符串数组,数组中的字符串就是 attribute 的名称
  • 方式二:对象类型,对象类型我们可以在指定 attribute 名称的同时,指定它需要传递的类型、是否是必须的、默认值等等
export default {
// props: ["title", "message"].
props: {
title: {
type: String,
required: true,
default: "我是title默认值",
}
}
}

type 的类型

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

对象类型的其他写法

Prop 的大小写命名

  • HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符
  • 当使用 DOM 中的模板时, camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
<div>
<show-message messageInfo="哈哈哈"></show-message>
<show-message message-info="哈哈哈"></show-message>
</div>

非 Prop 的 Attribute

  • 传递给一个组件某个属性,但是该属性并没有定义对应的 props 或者 emits 时,就称之为非 Prop 的 Attribute
  • 常见的包括 class、 style、 id 属性等
  • 当组件有单个根节点时,非 Prop 的 Attribute 将自动添加到根节点的 Attribute中

禁用 Attribute 继承和多根节点

  • 不希望组件的根元素继承 attribute,可以在组件中设置 inheritAttrs: false
  • 禁用 attribute 继承的常见情况是需要将 attribute 应用于根元素之外的其他元素
  • 我们可以通过 $attrs 来访问所有的非 props 的 attribute
<div>
我是NotPropAttribute组件
<h2 :class="$attrs.class"></h2>
</div>
  • 多个根节点的 attribute 如果没有显示的绑定,那么会报警告,我们必须手动的指定要绑定到哪一个属性上
<template>
<div :class="$attrs.class">我是NotPropAttribute组件1</div>
<div>我是NotPropAttribute组件2</div>
<div>我是NotPropAttribute组件3</div>
</template>

父子通信-子传父

当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容

子组件有一些内容想要传递给父组件的时候

操作:

  • 首先,我们需要在子组件中定义好在某些情况下触发的事件名称
  • 其次,在父组件中以 v-on 的方式传入要监听的事件名称,并且绑定到对应的方法中
  • 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件

代码演示:

子组件:

<template>
  <div class="add">
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
  </div>
</template>
<script>
  export default {
    // 1.emits数组语法, 显示所有发射出去的方法
    emits: ["add"],
    // 2.emmits对象语法
    // emits: {
    //   add: function(count) {
    //     if (count <= 10) {
    //       return true
    //     }
    //     return false
    //   }
    // },
    methods: {
      btnClick(count) {
        console.log("btnClick:", count)
        // 让子组件发出去一个自定义事件
        // 第一个参数自定义的事件名称
        // 第二个参数是传递的参数
        this.$emit("add", 100)
      }
    }
  }
</script>
<style scoped>

</style>

父组件:

<template>
  <div class="app">
    <h2>当前计数: {{ counter }}</h2>
    <!-- 1.自定义add-counter, 并且监听内部的add事件 -->
    <add-counter @add="addBtnClick"></add-counter>
    <!-- 2.自定义sub-counter, 并且监听内部的sub事件 -->
    <sub-counter @sub="subBtnClick"></sub-counter>
  </div>
</template>
<script>
  import AddCounter from './AddCounter.vue'
  import SubCounter from './SubCounter.vue'
  export default {
    components: {
      AddCounter,
      SubCounter
    },
    data() {
      return {
        counter: 0
      }
    },
    methods: {
      addBtnClick(count) {
        this.counter += count
      },
      subBtnClick(count) {
        this.counter -= count
      }
    }
  }
</script>
<style scoped>
</style>

非父子组件通信

除了父子组件之间的通信之外,还会有非父子组件之间的通信, 有两种方式:

  • 全局事件总线
  • Provide/Inject

当然, 更常见的是使用 Vuex 或 Pinia 状态管理库

全局事件总线 mitt 库

Vue3 从实例中移除了 $on、 $off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库

  • Vue3 官方有推荐一些库,例如 mitt 或 tiny-emitter
  • 以 hy-event-store 为例

安装:

npm install hy-event-bus

封装一个工具 eventbus.js

import { HYEventBus } from 'hy-event-store'

const eventBus = new HYEventBus()

export default eventBus

使用

  • 在 App. vue 中监听事件
  • 在 Banner. vue 中触发事件

取消之前注册的函数监听

cancelListener() {
eventBus.off("bannerClick", this.bannerClick)
}

Provide 和 Inject

用于非父子组件之间共享数据

  • 比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容
  • 仍然将 props 沿着组件链逐级传递会很麻烦

可以使用 Provide 和 Inject

  • 无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者
  • 父组件有一个 provide 选项来提供数据
  • 子组件有一个 inject 选项来开始使用这些数据

使用