vue3的单组件编写【一】
文章目录
- :tiger: 单组件的编写
- :rainbow:全新的 setup 函数
- :rocket: setup 的含义
- :rocket:setup 的参数使用
- :rocket: defineComponent 的作用
- :rainbow: 组件的生命周期
- :rocket: 升级变化
- :rocket: 使用 3.x 的生命周期
- :rainbow: 组件的基本写法
- :rocket: 回顾 Vue 2
- :rocket: 了解 Vue 3
🐯 单组件的编写
项目搭好了,第一个需要了解的是 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.foo
、 this.bar()
这样来获取实例上的数据,或者执行实例上的方法。
在使用
setup
的情况下 , 不能再用this
来获取 Vue 实例;在使用
setup
的情况下 , 不能再用this
来获取 Vue 实例’;在使用
setup
的情况下 , 不能再用this
来获取 Vue 实例;
🚀setup 的参数使用
setup
函数包含了两个入参:
参数 | 类型 | 含义 | 是否必传 |
---|---|---|---|
props | object | 由父组件传递下来的数据 | 否 |
context | object | 组件的执行上下文 | 否 |
第一个参数 props
:
它是响应式的,当父组件传入新的数据时,它将被更新。
⭐️ ⭐️⭐️ 请不要解构它,这样会让数据失去响应性,一旦父组件发生数据变化,解构后的变量将无法同步更新为最新的值。
可以使用 Vue 3 全新的响应式 API toRef / toRefs 进行响应式数据转换,下文将会介绍全新的响应式 API 的用法。
第二个参数 context
:
context
只是一个普通的对象,它暴露三个组件的 Property :
属性 | 类型 | 作用 |
---|---|---|
attrs | 非响应式对象 | 未在 Props 里定义的属性都将变成 Attrs |
slots | 非响应式对象 | 组件插槽,用于接收父组件传递进来的模板内容 |
emit | 方法 | 触发父组件绑定下来的事件 |
因为 context
只是一个普通对象,所以可以直接使用 ES6 解构。
平时使用可以通过直接传入 { emit }
,即可用 emit('xxx')
来代替使用 context.emit('xxx')
,另外两个功能也是如此。
但是 attrs
和 slots
请保持 attrs.xxx
、slots.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 生命周期 | 执行时间说明 |
---|---|---|
beforeCreate | setup | 组件创建前执行 |
created | setup | 组件创建后执行 |
beforeMount | onBeforeMount | 组件挂载到节点上之前执行 |
mounted | onMounted | 组件挂载完成后执行 |
beforeUpdate | onBeforeUpdate | 组件更新之前执行 |
updated | onUpdated | 组件更新完成之后执行 |
beforeDestroy | onBeforeUnmount | 组件卸载之前执行 |
destroyed | onUnmounted | 组件卸载完成后执行 |
errorCaptured | onErrorCaptured | 当捕获一个来自子孙组件的异常时激活钩子函数 |
可以看到 Vue 2 生命周期里的 beforeCreate
和 created
,在 Vue 3 里已被 setup
替代。
熟悉 Vue 2 的开发者应该都知道 Vue 有一个全局组件 <KeepAlive />
,用于在多个组件间动态切换时缓存被移除的组件实例,当组件被包含在 <KeepAlive />
组件里时,会多出两个生命周期钩子函数:
Vue 2 生命周期 | Vue 3 生命周期 | 执行时间说明 |
---|---|---|
activated | onActivated | 被激活时执行 |
deactivated | onDeactivated | 切换组件后,原组件消失前执行 |
⭐️ 虽然 Vue 3 依然支持 Vue 2 的生命周期,但是不建议混搭使用,前期可以继续使用 Vue 2 的生命周期作为过度阶段慢慢适应,但还是建议尽快熟悉并完全使用 Vue 3 的生命周期编写组件。
🚀 使用 3.x 的生命周期
在 vue3 的 Composition API 写法里, 每个生命周期函数都要先导入才可以使用,并且所有的生命周期函数统一放在 setup 里面运行。
如果需要达到vue2 的 beforeCreate
和 created
生命周期的执行时机。 直接在 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 2 | Vue.extend | Template |
Vue 2 | Class Component | Template |
Vue 2 | Class Component | TSX |
使用 TypeScript 来声明三种不同的组件,可以按照以下方式编写:
- 使用
Vue.extend
和模板(Template)的基本写法:
import Vue from 'vue';
const MyComponent: Vue = Vue.extend({
template: '<div>{{ message }}</div>',
data() {
return {
message: 'Hello, Vue!'
};
}
});
export default MyComponent;
- 使用 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)
}
}
- 使用 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 3 | Class Component | Template | Vue 2 | × |
Vue 3 | defineComponent | Template | Vue 2 | × |
Vue 3 | defineComponent | Template | Vue 3 | √ |
Vue 3 | Class Component | TSX | Vue 2 | × |
Vue 3 | defineComponent | TSX | Vue 2 | × |
Vue 3 | defineComponent | TSX | Vue 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
。