Vue速通(3)

了解组件与组件通信

在日常开发时,始终应该以“高内聚,低耦合”设计思想实现需求,而组件化开发也是对这个原则的具体实现。对前端来说,将页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,且不利于后续的管理以及扩展,所以开发时需要对功能进行拆分,这就是组件化开发。目前前端流行的框架也多以组件化开发思想进行开发。

在 Vue 中注册组件分成两种:1. 全局组件:在任何其他的组件中都可以使用的组件;2. 局部组件:只有在注册的组件中才能使用的组件。

Vue 注册全局组件

注册全局组件可以用 app.component()进行注册,app.component()传入两个参数,(组件名称, 组件的对象)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<template id="item">
  <!-- 组件item的模板 -->
</template>

<script src="../lib/vue.js"></script>
<script>
  const App = {}

  const app = Vue.createApp(App)

  app.component('product-item', {
    template: '#item',
  })

  app.mount('#app')
</script>

全局注册的组件本身也有自己的代码逻辑,可以使用 data、computed、methods 等属性和方法。

在通过 app.component 注册一个组件的时候,第一个参数是组件的名称,定义组件名的方式有两种:1. 使用 kebab-case(短横线分割符);2. 使用 PascalCase(驼峰标识符)。

注册全局组件后,即使没有用到该组件,在打包后得到的 JavaScript 代码中仍然会包含该组件的代码。所以为了性能优化,大部分时候会选择局部注册组件。

Vue 注册局部组件

局部注册是在我们需要使用到的组件中,通过 components 属性选项来进行注册,components 选项对应的是一个对象,对象中的键值对是“组件的名称: 组件对象”。

 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
31
<template id="item">
  <!-- 组件item的模板 -->
</template>

<template id="nav">
  <!-- 组件nav的模板 -->
</template>

<script src="../lib/vue.js"></script>
<script>
  // 可以定义组件对象,然后注册组件的时候直接引用
  const ProductItem = {
    template: '#item',
  }

  const app = Vue.createApp({
    components: {
      ProductItem,
      HomeNav: {
        template: '#nav',
        components: {
          // 如果不在HomeNav内部注册ProductItem组件,
          // 就不能在Home内部使用ProductItem组件。
          ProductItem,
        },
      },
    },
  })

  app.mount('#app')
</script>

单文件组件

所以在真实开发中,我们可以通过一个后缀名为.vue 的 single-file components (单文件组件) 来解决,并且可以使用 webpack 或者 vite 或者 rollup 等构建工具来对其进行处理。

在这个组件中我们可以获得非常多的特性:代码的高亮;ES6、CommonJS 的模块化能力;组件作用域的 CSS;使用预处理器来构建更加丰富的组件,比如 TypeScript、Babel、Less、Sass 等。

在单文件组件开发模式下,全局注册组件在项目文件夹 main.js 下进行注册,局部注册组件在组件内部进行注册。

组件插槽

插槽是指将共同的元素、内容在组件内进行封装,将不同的元素使用 slot 作为占位,让外部决定到底显示什么样的元素。

Vue 中将<slotl>元素作为承载分发内容的出口。在封装组件中,使用特殊的元素<slotl>就可以为封装组件开启一个插槽,该插槽插入什么内容取决于父组件如何使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- 子组件 -->
<div>
  <slot>
    <p>默认内容</p>
  </slot>
</div>

<!-- 父组件 -->
<son-component>
  <button>按钮</button>
</son-component>

有多个插槽时通过,就可以使用具名插槽给插槽起一个名字。在子组件中使用 v-slot:(#)绑定父组件对应的插槽,v-slot:[dynamicSlotName]方式动态绑定一个名称。

 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
<!-- 子组件 -->
<div class="nav-bar">
  <div class="left">
    <slot name="left">left</slot>
  </div>
  <div class="center">
    <slot name="center">center</slot>
  </div>
  <div class="right">
    <slot name="right">right</slot>
  </div>
</div>

<!-- 父组件 -->
<nav-bar>
  <template #left>
    <button></button>
  </template>
  <template #center>
    <span></span>
  </template>
  <template v-slot:right>
    <a href="#"></a>
  </template>
</nav-bar>

组件的通信

父子组件通信

父组件传递给子组件:通过 props 属性。

 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
31
32
33
34
35
父组件引用子组件:
<template>
  <son-component pro="prop1" pro2="prop2" />
</template>

<script>
  import SonComponent from './SonComponent.vue'

  export default {
    components: {
      SonComponent,
    },
  }
</script>

子组件接受父组件props传值:
<script>
  export default {
    // 1.props数组语法 弊端: 1.不能对类型进行验证 2.没有默认值的
    // 对象类型写默认值时, 需要编写default的函数, 函数返回默认值
    props: ["prop1", "prop2"]

    // 2.props对象语法
    props: {
      pro1: {
        type: String,
        default: "我是prop1"
      },
      pro2: {
        type: Number,
        required: true
      },
    }
  }
</script>

子组件传递给父组件:通过$emit 触发事件。

 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
<!-- 子组件监听事件,并通过this.$emits()传出。this.$emit()接受两个参数,方法名以及参数。 -->
<script>
  export default {
    methods: {
      sonmethod(args) {
        this.$emit('sonmethod', args)
      },
    },
  }
</script>

<!-- 父组件自定义方法,并监听子组件传出的传出的方法 -->
<template>
  <son-component @son="parentmethod" />
</template>

<script>
  import SonComponent from './SonComponent.vue'

  export default {
    components: {
      SonComponent,
    },
    methods: {
      parentmethod(args) {
        return args
      },
    },
  }
</script>

Vue 官方建议我们在组件中所有的 emit 事件都能在组件的 emits 选项中声明。emits 参数有俩种形式对象和数组,对象里面可以配置带校验 emit 事件,为 null 的时候代表不校验。校验的时候,会把 emit 事件的参数传到校验函数的参数里面。当校验函数不通过的时候,控制台会输出一个警告,但是 emit 事件会继续执行。

非父子组件通信

祖先组件与后代组件:

provide 和 inject。组件组件使用 provide 方法(options api)后,后代组件使用 inject(数组)即可接收祖先组件传来的数据。

事件总线:子组件引用引用事件总线工具后使用 event.emit 传出,父组件使用 eventBus.on 接收事件,组件销毁时,可以使用 eventBus.off 取消事件。