[TOC]

组件的拆分

我们可以按照如下的方式进行拆分:
image.png
按照如上的拆分方式后,我们开发对应的逻辑只需要去对应的组件编写就可。

组件样式

我们通常会给 style 标签添加 scoped 。通过添加属性选择器达到样式作用域的目的。但是 vue3 有个缺陷,因为 vue3 不要求 template 模板中必须有个根标签,所以父组件中 vue-loader 自动添加的 data-123456 自定义属性也会直接添加到子组件“有实际作用”的标签上。
vue2 时模板中都有一个根组件包裹,所以父组件的自定义属性只会添加到子组件的根标签上,这样父组件样式就无法作用子组件。

<template>
    <h1> 父组件 </h1>
    <hello-world></hello-world>
</template>

<script>
  import HelloWorld from './components/HelloWorld.vue'

  export default {
    components: { HelloWorld },

  }
</script>

<style scoped>
h1{
  color: black;
}
</style>
<template>
  <h2> 子组件 </h2>
</template>

<style scoped>
  h1 {
    color: aqua;
  }
</style>

image.png
父组件的自定义属性添加到了子组件本不应该被添加属性的元素上,导致父组件样式覆盖子组件样式。
解决办法:

  1. 和 vue2 一样手动给子组件添加一个根标签
  2. 定义样式的时候,使用类进行精确定义

    组件的通信

    上面组件的拆分方式:
  • App组件是Header、Main、Footer组件的父组件;
  • Main组件是Banner、ProductList组件的父组件;

在开发过程中,我们会经常遇到需要组件之间相互进行通信:

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

    父子组件之间通信的方式

    父组件传递给子组件:通过props属性;
    子组件传递给父组件:通过$emit触发事件;
    image.png

    父组件传递给子组件

    在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示:这个时候我们可以通过 props 来完成组件之间的通信;
    什么是 Props 呢?

  • props 是你可以在组件上注册一些自定义的 attribute;

  • 父组件给这些 attribute 赋值,子组件通过 attribute 的名称获取到对应的值;

Props 有两种常见的形式:

  • 方式一:字符串数组,数组中的字符串就是 attribute 的名称;
  • 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;

    props 是 properties 的缩写,property 和 attribute 都是属性的意思,但是 property 更趋向于对象的属性,attribute 趋向于标签元素的属性,如 class style 等。

父组件传值给子组件,就相当于通过添加子组件的标签属性(attribute)从而给子组件的 VNode 对象动态添加属性(property)。所以子组件接收的是 props。
添加属性也有两种方式:

  1. 一个一个添加
  2. v-bind=对象批量添加 ```html

    ——————sonComponent1——————-

    ——————sonComponent2——————-

    ``` ### Props 的对象用法 数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,
    当 props 使用对象语法的时候,我们可以对传入的内容进行限制,虽然限制不是强制的,但会报警告: - 比如指定传入的attribute的类型; - 比如指定传入的attribute是否是必传的; - 比如指定没有传入时,attribute的默认值; 对象用法也有很多写法:
    ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1651658180142-0da833ae-5cac-4d72-b0ae-d254a14abecc.png#clientId=u20e9f464-d26e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=350&id=ue99360a1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=481&originWidth=674&originalType=binary&ratio=1&rotation=0&showTitle=false&size=153362&status=done&style=none&taskId=u9a2b5bf5-2961-4b5d-8c9d-0332425e5b4&title=&width=490.1818181818182)
    **type的类型都可以是哪些呢?**
    String、Number、Boolean、Array、Object、Date、Function、Symbol
    注意:
    如果 type 是对象 object 类型,则默认值要**写成函数形式**。因为一个组件可能会使用多次,而键值对的方式是一个浅拷贝,组件中对对象的修改会互相影响。写成一个函数,每次使用组件都会执行一次函数,获取一个全新的对象。不会互相干扰。
    ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1651658219368-0319ce38-ca8c-4f90-9691-d82e13498068.png#clientId=u20e9f464-d26e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=474&id=u0346c911&margin=%5Bobject%20Object%5D&name=image.png&originHeight=652&originWidth=730&originalType=binary&ratio=1&rotation=0&showTitle=false&size=217206&status=done&style=none&taskId=ubc73459d-4d42-4501-b3be-fb36651fc45&title=&width=530.9090909090909) ### Prop 的大小写命名 HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符;所以在给标签添加 attribute 属性的时候,如果是驼峰的形式,被浏览器一转成小写,可 props 中还是驼峰的形式,这就可能导致 props 拿不到传过来的值。
    所以推荐给标签添加 attribute 属性的时候写成短横线分隔的形式。虽然现在在 vue 文件中写成驼峰的形式也能拿到数据,但是那是因为 vue-loader 做了处理。 ```html ``` ### 非 Prop 的 Attribute 什么是非Prop的Attribute呢?
    当我们传递给一个组件某个属性,但是该属性并没有定义对应的 props 或者 emits 来接收时,这个被传递过来的属性就称之为 非Prop 的 Attribute;常见的是 class、style、id属性等; **没被 props 接收的属性具有继承性**。
    它被传入子组件后,就会被子组件模板中最顶层的 dom 所继承。 ```html ----------------------- ``` 如果我们不希望组件的根元素继承attribute,可以在组件中设置 `inheritAttrs: false`: - 禁用 attribute 继承的常见情况:是需要将 attribute 应用于根元素之外的其他元素; - 未被 props 接收的 attribute 属性被放入 `$attrs`对象中,我们可以通过 `$attrs`对象来访问 ```html ``` 如果存在多个根标签,让非 props 的 attribute 进行自动继承会报警告,因为不知道继承给哪个根标签。所以还是要通过`$attrs`进行手动指定应用。 ```html ``` ## 子组件传递给父组件 什么情况下子组件需要传递内容到父组件呢? - 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容; - 子组件有一些内容想要传递给父组件的时候; 子组件向父组件传值的核心就是自定义事件在子组件触发,而父组件能监听到该事件,并进行响应处理。 - 首先,我们需要在子组件中定义好在某些情况下触发的事件名称; - `emits`属性中定义好自定义事件的名称 - `this.$emit(自定义事件, 参数)`函数触发自定义事件 - 其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中; - 最后,在子组件中发生某个事件的时候,响应函数触发自定义事件,父组件就能监听到自定义事件,然后执行对应的响应函数。 ```html

    ————————-父组件———————

    ``` ### 参数验证 `emits`属性不光有数组形式,还能写成对象,对象的时候能对参数进行一些校验。校验不是强制性的,不符合要求只会发出警告。一般还是用数组的形式多一点。 ```html ``` ## 非父子组件的通信 有两种方式: 1. Provide/Inject; 2. Mitt全局事件总线; ### Provide 和 Inject Provide/Inject用于非父子组件之间共享数据:
    比如有一些深度嵌套的组件,子组件想要获取父组件的部分内容;在这种情况下,如果我们仍然将props沿着组件链逐级传递下去,就会非常的麻烦;对于这种情况下,我们可以使用 Provide 和 Inject :无论层级结构有多深,父组件都可以作为其所有子组件的依赖提供者; - 父组件有一个 provide 选项来提供数据; - 子组件有一个 inject 选项来开始使用这些数据; 实际上,你可以将依赖注入看作是“long range props”,只是不同的地方在于:父组件不需要知道哪些子组件使用它 provide 的 property,子组件不需要知道 inject 的 property 来自哪里。
    ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22919157/1651723053758-1bf9e77e-cb88-4ff9-bd71-02277e172f7c.png#clientId=ud4da78c7-49c6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=287&id=u21e77cbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=394&originWidth=522&originalType=binary&ratio=1&rotation=0&showTitle=false&size=72563&status=done&style=none&taskId=uae98a48e-ddb2-4549-bb32-42e15dbc337&title=&width=379.6363636363636) ```html -----------子或孙组件-------- ``` #### provide 中的 this 指向 前面我们将 provide 写成对象的形式,对象是没有作用域的,这个时候 provide 中的 this 指向的是

    —————-子或孙组件————

    ``` ## 全局事件总线 mitt 库 当我们想要在一个组件中监听响应其他组件触发的自定义事件的时候,我们可以使用事件总线。事件总线是全局的,所以没有什么父子组件的限制。
    Vue2 能直接使用事件总线,但是 Vue3 从实例中移除了 $on、$off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:Vue3官方有推荐一些库,例如 mitt 或 tiny-emitter; 这里我们主要讲解一下mitt库的使用;
    先安装`npm i mitt`,然后我们可以封装一个工具 eventbus.js。(bus 操作系统中翻译为总线) ```javascript import mitt from 'mitt' // 导入mitt构造函数 const eventer = new mitt() // 实例化事件总线对象 export default eventer ``` - `event.emit("自定义事件名", 参数)`触发自定义事件 - `event.on("自定义事件名", 响应函数fn)`响应自定义事件 ```html

    —————-其他组件—————

    <a name="kBOl3"></a>
    ### 取消自定义事件的监听
    
    - `eventer.all.clear()`取消所有自定义事件的监听
    - `eventer.off(事件, 事件响应函数的引用)`取消单个自定义事件的监听
    ```html
    <script>
    import sonComponent from './components/sonComponent.vue'
    import eventer from './util/eventbus'
      export default { 
        components: { sonComponent },
        data() {
          return {
            fn: function() { console.log(123);}
          }
        },
        created() {   
          // 单独取消
          eventer.off('sendmsg', this.fn)
          // 全部取消
          // eventer.all.clear()
        },
      }
    </script>