vue3的单组件编写【一】

🐯 单组件的编写

项目搭好了,第一个需要了解的是 Vue 组件的变化,由于这部分篇幅会非常大,所以会分成很多个小节,一部分一部分按照开发顺序来逐步了解。

因为 Vue 3 对 TypeScript 的支持真的是太完善了,并且 TypeScript 的发展趋势和市场需求度越来越高,所以接下来都将直接使用 TypeScript 进行编程。

对 TypeScript 不太熟悉的开发者,建议先阅读 快速上手 TypeScript 一章,有了一定的语言基础之后,再一边写代码一边加深印象。

🌈全新的 setup 函数

在开始编写 Vue 组件之前,需要了解两个全新的前置知识点:

  • 全新的 setup 函数,关系到组件的生命周期和渲染等问题
  • 写 TypeScript 组件离不开的 defineComponent API

🚀 setup 的含义

Vue 3 的 Composition API 系列里,推出了一个全新的 setup 函数,它是一个组件选项,在创建组件之前执行,一旦 props 被解析,并作为组合式 API 的入口点。

说的通俗一点,就是在使用 Vue 3 生命周期的情况下,整个组件相关的业务代码,都可以放在 setup 里执行。

因为在 setup 之后,其他的生命周期才会被启用。

基本语法:

// 这是一个基于 TypeScript 的 Vue 组件
import { defineComponent } from 'vue'

export default defineComponent({
  setup(props, context) {
    // 在这里声明数据,或者编写函数并在这里执行它

    return {
      // 需要给 `<template />` 用的数据或函数,在这里 `return` 出去
    }
  },
})

可以发现在这段代码里还导入了一个 defineComponent API ,也是 Vue 3 带来的新功能,下文的defineComponent 的作用 将介绍其用法。

在使用 setup 的情况下,请牢记一点:不能再用 this 来获取 Vue 实例,也就是无法和 Vue 2 一样,通过 this.foothis.bar() 这样来获取实例上的数据,或者执行实例上的方法。

在使用 setup 的情况下 , 不能再用 this 来获取 Vue 实例;

在使用 setup 的情况下 , 不能再用 this 来获取 Vue 实例’;

在使用 setup 的情况下 , 不能再用 this 来获取 Vue 实例;

🚀setup 的参数使用

setup 函数包含了两个入参:

参数类型含义是否必传
propsobject由父组件传递下来的数据
contextobject组件的执行上下文

第一个参数 props

它是响应式的,当父组件传入新的数据时,它将被更新。

⭐️ ⭐️⭐️ 请不要解构它,这样会让数据失去响应性,一旦父组件发生数据变化,解构后的变量将无法同步更新为最新的值。

可以使用 Vue 3 全新的响应式 API toRef / toRefs 进行响应式数据转换,下文将会介绍全新的响应式 API 的用法。

第二个参数 context

context 只是一个普通的对象,它暴露三个组件的 Property :

属性类型作用
attrs非响应式对象未在 Props 里定义的属性都将变成 Attrs
slots非响应式对象组件插槽,用于接收父组件传递进来的模板内容
emit方法触发父组件绑定下来的事件

因为 context 只是一个普通对象,所以可以直接使用 ES6 解构。

平时使用可以通过直接传入 { emit } ,即可用 emit('xxx') 来代替使用 context.emit('xxx'),另外两个功能也是如此。

但是 attrsslots 请保持 attrs.xxxslots.xxx 的方式来使用其数据,不要进行解构,虽然这两个属性不是响应式对象,但对应的数据会随组件本身的更新而更新。

两个参数的具体使用,可查阅 组件之间的通信 一章详细了解。

🚀 defineComponent 的作用

defineComponent 是 Vue 3 推出的一个全新 API ,可用于对 TypeScript 代码的类型推导,帮助开发者简化掉很多编码过程中的类型声明。

比如,原本需要这样才可以使用 setup 函数:

import { Slots } from 'vue'

// 声明 `props` 和 `return` 的数据类型
interface Data {
  [key: string]: unknown
}

// 声明 `context` 的类型
interface SetupContext {
  attrs: Data
  slots: Slots
  emit: (event: string, ...args: unknown[]) => void
}

// 使用的时候入参要加上声明, `return` 也要加上声明
export default {
  setup(props: Data, context: SetupContext): Data {
    // ...

    return {
      // ...
    }
  },
}

每个组件都这样进行类型声明,会非常繁琐,如果使用了 defineComponent , 就可以省略这些类型声明:

import { defineComponent } from "vue"
// 使用 `defineComponent` 包裹组件的内部逻辑
export default defineComponent({
  setup(props, context) {
    // ...

    return {
      // ...
    }
  },
})

代码量瞬间大幅度减少,只要是 Vue 本身的API , defineComponent 都可以自动推导其类型,这样开发和在编写组件的过程中,只需要维护自己定义的数据类型就可以了,可专注于业务。

🌈 组件的生命周期

在了解了 Vue 3 组件的两个前置知识点后,不着急写组件,还需要先了解组件的生命周期,这个知识点非常重要,只有理解并记住组件的生命周期,才能够灵活的把控好每一处代码的执行,使程序的运行结果可以达到预期。

🚀 升级变化

从 Vue 2 升级到 Vue 3 ,在保留对 Vue 2 的生命周期支持的同时,Vue 3 也带来了一定的调整。

Vue2 的生命周期写法名称是 Options API (选项式API) , Vue3 的生命周期写法名称叫 Composition API (组合式API) 。

Vue 3 组件默认支持 Options API ,而 Vue 2 可以通过 @vue/composition-api 插件获得 Composition API 的功能支持(其中 Vue 2.7 版本内置了该插件, 2.6 及以下的版本需要单独安装)。

为了减少理解成本,笔者将从读者的使用习惯上,使用 “ Vue 2 的生命周期” 代指 Options API 写法,用 “ Vue 3 的生命周期” 代指 Composition API 写法。

关于 Vue 生命周期的变化,可以从下表直观地了解:

Vue 2 生命周期Vue 3 生命周期执行时间说明
beforeCreatesetup组件创建前执行
createdsetup组件创建后执行
beforeMountonBeforeMount组件挂载到节点上之前执行
mountedonMounted组件挂载完成后执行
beforeUpdateonBeforeUpdate组件更新之前执行
updatedonUpdated组件更新完成之后执行
beforeDestroyonBeforeUnmount组件卸载之前执行
destroyedonUnmounted组件卸载完成后执行
errorCapturedonErrorCaptured当捕获一个来自子孙组件的异常时激活钩子函数

可以看到 Vue 2 生命周期里的 beforeCreatecreated ,在 Vue 3 里已被 setup 替代。

熟悉 Vue 2 的开发者应该都知道 Vue 有一个全局组件 <KeepAlive /> ,用于在多个组件间动态切换时缓存被移除的组件实例,当组件被包含在 <KeepAlive /> 组件里时,会多出两个生命周期钩子函数:

Vue 2 生命周期Vue 3 生命周期执行时间说明
activatedonActivated被激活时执行
deactivatedonDeactivated切换组件后,原组件消失前执行

⭐️ 虽然 Vue 3 依然支持 Vue 2 的生命周期,但是不建议混搭使用,前期可以继续使用 Vue 2 的生命周期作为过度阶段慢慢适应,但还是建议尽快熟悉并完全使用 Vue 3 的生命周期编写组件。

🚀 使用 3.x 的生命周期

在 vue3 的 Composition API 写法里, 每个生命周期函数都要先导入才可以使用,并且所有的生命周期函数统一放在 setup 里面运行。

如果需要达到vue2 的 beforeCreatecreated 生命周期的执行时机。 直接在 setup 里执行函数即可。

以下是几个生命周期的执行顺序对比:

import { defineComponent, onBeforeMount, onMounted } from 'vue'

export default defineComponent({
  setup() {
    console.log(1)

    onBeforeMount(() => {
      console.log(2)
    })

    onMounted(() => {
      console.log(3)
    })

    console.log(4)
  },
})

最终将按照生命周期的顺序输出:

// 1
// 4
// 2
// 3

🌈 组件的基本写法

如果想在 Vue 2 里使用 TypeScript 编写组件,需要通过 Options API 的 Vue.extend 语法,或者是另外一种风格 Class Component 的语法声明组件,其中为了更好的进行类型推导, Class Component 语法更受开发者欢迎。

但是 Class Component 语法和默认的组件语法相差较大,带来了一定的学习成本,对于平时编写 JavaScript 代码很少使用 Class 的开发者,适应时间应该也会比较长。

因此 Vue 3 在保留对 Class Component 支持的同时,推出了全新的 Function-based Component ,更贴合 JavaScript 的函数式编程风格,这也是接下来要讲解并贯穿全文使用的 Composition API 新写法。

Composition API 虽然也是一个变化比较大的改动,但其组件结构并没有特别大的变化,区别比较大的地方在于组件生命周期和响应式 API 的使用,只要掌握了这些核心功能,上手 Vue 3 非常容易!

看到这里可能有开发者心里在想:

“这几种组件写法,加上视图部分又有 Template 和 TSX 的写法之分,生命周期方面 Vue 3 对 Vue 2 的写法又保持了兼容,在 Vue 里写 TypeScript 的组合方式一只手数不过来,在入门时选择合适的编程风格就遇到了困难,可怎么办?”

不用担心!笔者将九种常见的组合方式以表格的形式进行对比, Vue 3 组件最好的写法一目了然!

🚀 回顾 Vue 2

下三种写法声明 TypeScript 组件:

适用版本基本写法视图写法
Vue 2Vue.extendTemplate
Vue 2Class ComponentTemplate
Vue 2Class ComponentTSX

使用 TypeScript 来声明三种不同的组件,可以按照以下方式编写:

  1. 使用 Vue.extend 和模板(Template)的基本写法:
import Vue from 'vue';

const MyComponent: Vue = Vue.extend({
  template: '<div>{{ message }}</div>',
  data() {
    return {
      message: 'Hello, Vue!'
    };
  }
});

export default MyComponent;
  1. 使用 Class Component 和模板(Template)的写法:

为了更好地获得 TypeScript 类型推导支持,通常使用 Class Component 的写法,这是 Vue 官方推出的一个装饰器插件(需要单独安装):

import Vue from 'vue';
import Component from 'vue-class-component';

// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
  // 所有的组件选项都可以放在这里
  template: '<div>{{ message }}</div>'
})

// 使用 Class 声明一个组件
export default class MyComponent extends Vue {
    
  // 初始数据可以直接声明为实例的 property
  message: string = 'Hello, Vue!';
    
  // 组件方法也可以直接声明为实例的方法
  onClick(): void {
    window.alert(this.message)
  }
}
  1. 使用 Class Component 和 TSX 的写法:
import Vue, { VueConstructor } from 'vue';
import Component from 'vue-class-component';

// @Component 修饰符注明了此类为一个 Vue 组件
@Component
export default class MyComponent extends Vue {
  message: string = 'Hello, Vue!';

  render(h: VueConstructor['createElement']) {
    return <div>{this.message}</div>;
  }
}

可在 Vue 2 官网的 TypeScript 支持 一章了解更多配置说明。

🚀 了解 Vue 3

Vue 3 从设计初期就考虑了 TypeScript 的支持,其中 defineComponent 这个 API 就是为了解决 Vue 2 对 TypeScript 类型推导不完善等问题而推出的。

在 Vue 3 ,至少有以下六种写法可以声明 TypeScript 组件:

适用版本基本写法视图写法生命周期版本官方是否推荐
Vue 3Class ComponentTemplateVue 2×
Vue 3defineComponentTemplateVue 2×
Vue 3defineComponentTemplateVue 3
Vue 3Class ComponentTSXVue 2×
Vue 3defineComponentTSXVue 2×
Vue 3defineComponentTSXVue 3

其中 defineComponent + Composition API + Template 的组合是 Vue 官方最为推荐的组件声明方式,本书接下来的内容都会以这种写法作为示范案例,也推荐开发者在学习的过程中,使用该组合进行入门。

下面看看如何使用 Composition API 编写一个最简单的 Hello World 组件:

<!-- Template 代码和 Vue 2 一样 -->
<template>
  <p class="msg">{{ msg }}</p>
</template>

<!-- Script 代码需要使用 Vue 3 的新写法-->
<script lang="ts">
// Vue 3 的 API 需要导入才能使用
import { defineComponent } from 'vue'

// 使用 `defineComponent` 包裹组件代码
// 即可获得完善的 TypeScript 类型推导支持
export default defineComponent({
  setup() {
    // 在 `setup` 方法里声明变量
    const msg = 'Hello World!'

    // 将需要在 `<template />` 里使用的变量 `return` 出去
    return {
      msg,
    }
  },
})
</script>

<!-- CSS 代码和 Vue 2 一样 -->
<style scoped>
.msg {
  font-size: 14px;
}
</style>

可以看到 Vue 3 的组件也是 <template /> + <script /> + <style /> 的三段式组合,上手非常简单。

其中 Template 沿用了 Vue 2 时期类似 HTML 风格的模板写法, Style 则是使用原生 CSS 语法或者 Less 等 CSS 预处理器编写。

⭐️ 需要注意的是,在 Vue 3 的 Composition API 写法里,数据或函数如果需要在 <template /> 中使用,就必须在 setup 里将其 return 出去,而仅在 <script /> 里被调用的函数或变量,不需要渲染到模板则无需 return

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个使用Vue3父子组件编写三级联动的示例代码: 父组件代码: ``` <template> <div> <ChildComp :options="options" v-model="selected"></ChildComp> </div> </template> <script> import ChildComp from './ChildComp.vue' export default { components: { ChildComp }, data() { return { options: [ { label: '省份1', value: 1, children: [ { label: '城市1-1', value: 11, children: [ { label: '区县1-1-1', value: 111 }, { label: '区县1-1-2', value: 112 } ] }, { label: '城市1-2', value: 12, children: [ { label: '区县1-2-1', value: 121 }, { label: '区县1-2-2', value: 122 } ] } ] }, { label: '省份2', value: 2, children: [ { label: '城市2-1', value: 21, children: [ { label: '区县2-1-1', value: 211 }, { label: '区县2-1-2', value: 212 } ] }, { label: '城市2-2', value: 22, children: [ { label: '区县2-2-1', value: 221 }, { label: '区县2-2-2', value: 222 } ] } ] } ], selected: [] } } } </script> ``` 子组件代码: ``` <template> <div> <select v-model="value" v-if="options.length > 0"> <option value="">请选择</option> <option v-for="option in options" :key="option.value" :value="option.value">{{option.label}}</option> </select> </div> </template> <script> export default { props: { options: { type: Array, default: () => [] }, value: { type: [String, Number], default: '' } }, watch: { value(newValue) { this.$emit('input', newValue) } }, mounted() { this.$emit('input', this.options[0].value) } } </script> ``` 以上代码实现了一个三级联动,父组件传入一个options数组作为选项,每个选项都包含一个label、value和children属性,分别代表选项的文本、值和子选项。子组件利用props接收options和value属性,通过v-model实现双向绑定,同时通过watch监听value的变化,当value改变时触发input事件向父组件传递新值。在子组件的mounted钩子函数中,将默认值设置为第一个选项的值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wusp1994

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值