Vue3.x 从零开始(二)—— 重新认识 Vue 组件

  • A+
所属分类:Web前端
摘要

Vue 3 更新了许多组件中的语法,包括生命周期、filter、setup、teleport 等 为了介绍这些特性,需要先了解一下 Vue 组件的基本玩法

Vue 3 更新了许多组件中的语法,包括生命周期、filter、setup、teleport 等

为了介绍这些特性,需要先了解一下 Vue 组件的基本玩法

这篇文章介绍的内容基本都是沿用 Vue 2 的语法,只在一些细节上有所调整

 

一、单文件组件

通过 Vue.createApp() 创建的应用,可以使用 component 方法创建自定义组件

const app = Vue.createApp({}); app.component('my-component', {   data() {     return {       name: 'Wise.Wrong'     }   },   template: `<div>hello {{name}}</div>`, });

不过在实际项目中,都是使用“单文件组件”,也就是以 .vue 文件的形式开发

比如上面组件,改写成单文件组件就是这样的:

<template>   <div>hello {{ name }}</div> </template>  <script lang="ts"> /* 当前文件路径: src/components/my-component.vue */ import { defineComponent } from 'vue';
export
default defineComponent({ name: 'MyComponent', data() { return { name: 'Wise.Wrong', }; }, }); </script>

这里的 data 必须通过函数的形式返回一个对象,而不能直接使用对象

// 2.x 可以接收对象,但 3.x 的 data 只能是 function 

 

假如现在还有另一个组件 <home> ,我们希望在 <home> 组件中使用 <my-component>

可以直接在 <home> 中引入 my-component.vue 文件,然后通过 components 属性注册组件

Vue3.x 从零开始(二)—— 重新认识 Vue 组件

上面的代码中,我在 components 中使用的是大驼峰命名组件,而在 <template> 中使用的是短线命名

这是因为 HTML 本身不区分大小写,Vue 更建议我们在 DOM 中使用全小写的短线命名的方式

 

二、Props

现在我们有了一个父组件 <home> 与子组件 <my-component>

如果我想在父组件中向子组件传递参数,可以通过 Props

首先在子组件定义 props

Vue3.x 从零开始(二)—— 重新认识 Vue 组件

然后父组件传入对应的 props 属性值

Vue3.x 从零开始(二)—— 重新认识 Vue 组件

最终结果

Vue3.x 从零开始(二)—— 重新认识 Vue 组件

除了定义数组形式的 props 之外,还可以使用对象类型,这样就能对 props 做更多限制

export default defineComponent({   // ...   props: {     likes: String, // 仅限定类型     years: {       type: [String, Number],       required: true, // 必填     },     info: {       type: Object,       default: () => ({ // 对象和数组类型的默认值只能用函数返回         title: '这里是对象类型的 prop',       }),     },     type: {       // 自定义校验函数       validator: (val: string) => (         ['success', 'warning', 'danger'].indexOf(val) !== -1       ),     },   },   // ... });

如果父组件传参不符合子组件中 props 定义的规则,Vue 会在控制台抛错

更多关于 Props 的使用可以查看官方文档

 

三、无状态组件

在子组件中,props 和 data 一样都是直接挂载到 this 对象下的,可以通过 this.xxx 的方式取值

对于某些功能型组件,只需要 props 不需要 data。比如这样的一个提示框组件:

<template>   <div :class="['alert', type]">     <div class="alert-content">{{content}}</div>   </div> </template>  <script lang="ts"> import { defineComponent } from 'vue';  export default defineComponent({   name: 'Alert',   props: ['type', 'content'], }); </script>

Vue3.x 从零开始(二)—— 重新认识 Vue 组件

Vue3.x 从零开始(二)—— 重新认识 Vue 组件

这种只接收 props,没有定义 data、methods、computed 等响应数据的组件,被称为无状态组件

在 Vue 2 中,无状态组件的初始化比普通组件快得多,所以经常会作为性能优化的一个考量

为了创建一个无状态组件,必须在 <template> 中声明 functional

<template functional> Vue 2 </template>

但是在 Vue 3 中,无状态组件不再需要声明 functional

只需要像一个普通组件一样定义,只是不要添加 data 等响应数据,就像上面的提示框组件那样

官方文档是这样解释的:

However, in Vue 3, the performance of stateful components has improved to the point that the difference is negligible.

但是,在Vue 3中,有状态组件的性能已提高到比肩无状态组件的程度。

emmmmmm... 相当的膨胀...

不过我喜欢~

 

四、Mixin

与无状态组件相对应的是有状态组件,也就是带有 data 等响应数据的组件,我们工作中写的组件大部分都是有状态组件

而随着业务的不断扩大,整个应用变得非常臃肿,这时候组件共用和逻辑抽取就尤为重要,这时候可以使用 mixin

 

假如我们有三个组件,它们的 js 部分都包含以下内容:

<script> export default {   // ...   data() {     return {       // ...       company: {         name: 'Perfect World',         address: 'Chongqing China',       },       loading: false,     }   },   props: {     // ...     title: String   },   methods: {     // ...     fetchCompanyInfo(data) {       this.loading = true;       return fetch('/api')         .then(res => res.json())         .then(res => {           this.company = { ...res.data };         })         .finally(_ => {            this.loading = false;         })     }   }, }; </script>

三个组件都包含响应数据(data) loading 和 company,以及相应的查询方法(methods) fetchInfo,还有一个来自父组件的参数(props) title

这些逻辑完全一样,如果要在三个组件中各写一份完全一样的代码,就会非常难受,不利于维护

有了 mixin 之后,我们就能把这些逻辑写在一个对象里面并导出

/* mixin-test.js */ export default {   data: () => ({     loading: false   }),   props: {     title: String   },   methods: {     fetchCompanyInfo(data) {       // ...     }   } }

然后在需要用到这部分逻辑的组件中引入,并注册到 mixins

mixins 接收的是一个数组,也就是说可以同时注册多个 mixin

<script> import MixinTest from "./mixin/mixin-test.js";  export default {   // ...   mixins: [ MixinTest ], }; </script>

Vue 在初始化组件的时候,会将引入的 mixin 对象和当前组件进行合并

在合并的时候如果遇到了同名选项,对于不同的类型 (data、props、methods) 会有不同的策略

1. 如果是 props、methods、components、directives 冲突,会以组件本身的键值对覆盖 mixin

2. 对于生命周期这样的钩子函数出现冲突,会先触发 mixin 中的钩子函数,然后触发组件中的钩子函数

3. 如果 data 发生冲突,将执行浅层次的合并:

const Mixin = {   data: ()=> ({     user: { name: 'Jack', id: 1 }   }) }  const Comp = {   mixins: [Mixin],   data: () => ({     user: { id: 2 }   }) }  // 最终的结果是: {   user: { id: 2 } }

而在 Vue 2 中,data 会执行深层次的合并,上例的结果会变成:

{   user: { id: 2, name: 'Jack' } }

在 Vue 2 的时代,Mixin 是封装的共用逻辑的常用手段,但这种方式一直都受到很多开发者的质疑

首先就是 mixin 的重名问题,虽然 Vue 有一套合并策略(这个合并策略可以自定义)来处理 mixin 的重名问题

但组件内部是无法知道 mixin 到底提供了哪些字段,如果后来的维护人员不熟悉组件中的 mixin,在维护代码的时候很容易因为重名的问题导致 bug

而且 mixin 可以使用组件中的数据,如果维护组件的时候更改了 mixin 所依赖的数据,也会导致 bug

为了解决这些问题,Vue 3 推出了 Composition API ,用函数式编程的思想来封装组件逻辑

欲知 Composition API 如何,请听下回分解~