本文主要是讲解TypeScript的基本使用。

是什么?

是在单文件组件 (SFC) 中使用composition api的编译时语法糖。

本文写作时,vue使用的3.2.26版本。

1.1. 发展历程

我们先看看vue3 的发展历程:

  • Vue3在早期版本(3.0.0-beta.21之前)中对composition api的支持,只能在组件选项setup函数中使用。

{{ msg }}

import { defineComponent, ref } from 'vue'import ComponentA from '@/components/ComponentA'import ComponentB from '@/components/ComponentB'export default defineComponent({name: 'HelloWorld',components: { ComponentA, ComponentB },props: {msg: String,},setup(props, ctx) {const count = ref(0)function add() {count.value++}// 使用return {} 把变量、方法暴露给模板return {count,add,}},})
  • 3.0.0-beta.21版本中增加了的实验特性。如果你使用了,会提示你还处在实验特性阶段。

  • 3.2.0版本中移除的实验状态,从此,宣告正式转正使用,成为框架稳定的特性之一。

import { ref } from 'vue'import ComponentA from '@/components/ComponentA'import ComponentB from '@/components/ComponentB'defineProps()const count = ref(0)function add() {count.value++}x

{{ msg }}

1.2. 优势

与组件选项setup函数对比,的优点:

  • 更少、更简洁的代码,不需要使用return {}暴露变量和方法了,使用组件时不需要主动注册了;
  • 更好的Typescript支持,使用纯Typescript声明props和抛出事件,不会再像option api里那么蹩脚了;
  • 更好的运行时性能;

当然,也是有自己的缺点的,比如需要学习额外的API

那么怎么使用呢?有哪些使用要点?与TypeScript如何结合?

2. 使用要点

2.1. 工具

Vue3单文件组件 (SFC) 的TS IDE支持请用 + VSCode + Volar

类型检查使用vue-tsc命令。

  • VSCode:前端最好用的IDE
  • Volar:为Vue3*.vue单文件组件提供代码高亮、语法提示等功能支持的VSCode插件;Vue2你可能是使用的Vetur插件,需要禁用Vetur,下载Volar,并启用它。
  • vue-tsc:类型检查和dts构建命令行工具。

2.2. 基本用法

setup属性添加到代码块上。

import { ref } from 'vue'defineProps({msg: String})const count = ref(0)function add() {count.value++}

{{ msg }}

若需要使用TypeScript,则将lang属性添加到代码块上,并赋值ts

import { ref } from 'vue'defineProps()const count = ref(0)function add() {count.value++}

{{ msg }}

块中的脚本会被编译成组件选项setup函数的内容,也就是说它会在每次组件实例被创建的时候执行。

声明的顶层绑定(变量、函数、import引入的内容),都会自动暴露给模板,在模板中直接使用。

import { ref } from 'vue'// 外部引入的方法,不需要通过 methods 选项来暴露它,模板可以直接使用import { getToken } from './utils'// 外部引入的组件,不需要通过 components 选项来暴露它,模板可以直接使用import ComponentA from '@/components/ComponentA'defineProps({msg: String})// 变量声明,模板可以直接使用const count = ref(0)// 函数声明,模板可以直接使用function add() {count.value++}

{{ msg }}

{{ getToken() }}

注意:

  • 每个*.vue文件最多可同时包含一个块 (不包括);

  • 每个*.vue文件最多可同时包含一个块 (不包括常规的);

2.3. 编译器宏

编译器宏(compiler macros)有:definePropsdefineEmitswithDefaultsdefineExpose等。

编译器宏只能块中使用,不需要被导入,并且会在处理块时被一同编译掉。

编译器宏必须的顶层使用,不可以在的局部变量中引用。

defineProps

块中是没有组件配置项的,也就是说是没有props选项,需要使用defineProps来声明props相关信息。defineProps接收的对象和组件选项props的值一样。

const props = defineProps({msg: String,title: {type: String,default: '我是标题'},list: {type: Array,default: () => []}})// 在 js 中使用 props 中的属性console.log(props.msg)

{{ msg }}

{{ title }}

TS 版本:

interface ListItem {name: stringage: number}const props = defineProps()// 在 ts 中使用 props 中的属性,具有很好的类型推断能力console.log(props.list[0].age)

{{ msg }}

{{ title }}

从代码中可以发现TS写法里props没有定义默认值。

Vue3为我们提供了withDefaults这个编译器宏,给props提供默认值。

interface ListItem {name: stringage: number}interface Props {msg: string// title可选title?: stringlist: ListItem[]}// withDefaults 的第二个参数便是默认参数设置,会被编译为运行时 props 的 default 选项const props = withDefaults(defineProps(), {title: '我是标题',// 对于array、object需要使用函数,和以前的写法一样list: () => []})// 在 ts 中使用 props 中的属性,具有很好的类型推断能力console.log(props.list[0].age)

{{ msg }}

{{ title }}

一个需要注意的地方:在顶层声明一个和props的属性同名的变量,会有些问题。

const props = defineProps({title: {type: String,default: '我是标题'}})// 在顶层声明一个和props的属性title同名的变量const title = '123'{{ props.title }}{{ title }}

所以,和组件选项一样,不要定义和props的属性同名的顶层变量。

defineEmits

一样的,在块中也是没有组件配置项emits的,需要使用defineEmits编译器宏声明emits相关信息。

// ./components/HelloWorld.vuedefineProps({msg: String,})const emits = defineEmits(['changeMsg'])const handleChangeMsg = () => {emits('changeMsg', 'Hello TS')}

{{ msg }}

使用组件:

import { ref } from 'vue'import HelloWorld from './components/HelloWorld.vue'const msg = ref('Hello Vue3')const changeMsg = (v) => {msg.value = v}

TS 版本:

// ./components/HelloWorld.vuedefineProps()const emits = defineEmits()const handleChangeMsg = () => {emits('changeMsg', 'Hello TS')}

{{ msg }}

使用组件:

import { ref } from 'vue'import HelloWorld from './components/HelloWorld.vue'const msg = ref('Hello Vue3')const changeMsg = (v: string) => {msg.value = v}

defineExpose

Vue3中,默认不会暴露任何在中声明的绑定,即不能通过模板ref获取到组件实例声明的绑定。

Vue3提供了defineExpose编译器宏,可以显式地暴露需要暴露的组件中声明的变量和方法。

// ./components/HelloWorld.vueimport { ref } from 'vue'const msg = ref('Hello Vue3')const handleChangeMsg = (v) => {msg.value = v}// 对外暴露的属性defineExpose({msg,handleChangeMsg,})

使用组件:

import { ref, onMounted } from 'vue'import HelloWorld from './components/HelloWorld.vue'const root = ref(null)onMounted(() => {console.log(root.value.msg)})const handleChangeMsg = () => {root.value.handleChangeMsg('Hello TS')}

TS 版本:

// ./components/HelloWorld.vueimport { ref } from 'vue'const msg = ref('Hello Vue3')const handleChangeMsg = (v: string) => {msg.value = v}defineExpose({msg,handleChangeMsg})

{{ msg }}

使用组件:

import { ref, onMounted } from 'vue'import HelloWorld from './components/HelloWorld.vue'// 此处暂时使用any,需要定义类型const root = ref(null)onMounted(() => {console.log(root.value.msg)})const handleChangeMsg = () => {root.value.handleChangeMsg('Hello TS')}

2.4. 辅助函数

中常用的辅助函数hooks api,主要有:useAttrsuseSlotsuseCssModule,其他的辅助函数还在实验阶段,不做介绍。

useAttrs

在模板中使用$attrs来访问attrs数据,与Vue2相比,Vue3$attrs还包含了classstyle属性。

中使用useAttrs函数获取attrs数据。

import HelloWorld from './components/HelloWorld.vue'
// ./components/HelloWorld.vueimport { useAttrs } from 'vue'const attrs = useAttrs()// js中使用console.log(attrs.class) // hello-wordconsole.log(attrs.title) // 我是标题{{ $attrs.title }}

useSlots

在模板中使用$slots来访问slots数据。

中使用useSlots函数获取slots插槽数据。

import HelloWorld from './components/HelloWorld.vue'默认插槽具名插槽footer

import { useSlots } from 'vue'const slots = useSlots()// 在js中访问插槽默认插槽default、具名插槽footerconsole.log(slots.default)console.log(slots.footer)

useCssModule

Vue3中,也是支持CSS Modules的,在上增加module属性,即

代码块会被编译为CSS Modules并且将生成的 CSS 类作为$style对象的键暴露给组件,可以直接在模板中使用$style。而对于如具名CSS Modules,编译后生成的 CSS 类作为content对象的键暴露给组件,即module属性值什么,就暴露什么对象。

import { useCssModule } from 'vue'// 不传递参数,获取代码块编译后的css类对象const style = useCssModule()console.log(style.success) // 获取到的是success类名经过 hash 计算后的类名// 传递参数content,获取代码块编译后的css类对象const contentStyle = useCssModule('content')普通style red默认CssModule pink默认CssModule pink具名CssModule blue具名CssModule blue.success {color: red;}.success {color: pink;}.success {color: blue;}

注意,同名的CSS Module,后面的会覆盖前面的。

2.5. 使用组件

在组件选项中,模板需要使用组件(除了全局组件),需要在components选项中注册。

而在中组件不需要再注册,模板可以直接使用,其实就是相当于一个顶层变量。

建议使用大驼峰(PascalCase)命名组件和使用组件。

import HelloWorld from './HelloWorld.vue'

2.6. 组件name

是没有组件配置项name的,可以再使用一个普通的来配置name

// ./components/HelloWorld.vueexport default {name: 'HelloWorld'}import { ref } from 'vue'const total = ref(10){{ total }}

使用:

import HelloWorld from './components/HelloWorld.vue'console.log(HelloWorld.name) // 'HelloWorld'

注意: 如果你设置了lang属性,lang需要保持一致。

2.7. inheritAttrs

inheritAttrs表示是否禁用属性继承,默认值是true

是没有组件配置项 inheritAttrs 的,可以再使用一个普通的

import HelloWorld from './components/HelloWorld.vue'

./components/HelloWorld.vue

export default {name: 'HelloWorld',inheritAttrs: false,}import { useAttrs } from 'vue'const attrs = useAttrs()hover一下看titlehover一下看title

2.8. 顶层await支持

中可以使用顶层 await。结果代码会被编译成async setup()

const userInfo = await fetch(`/api/post/getUserInfo`)

注意:async setup()必须与Suspense组合使用,Suspense目前还是处于实验阶段的特性,其 API 可能随时会发生变动,建议暂时不要使用。

2.9. 命名空间组件

vue3中,我们可以使用点语法来使用挂载在一个对象上的组件。

// components/Form/index.jsimport Form from './Form.vue'import Input from './Input.vue'import Label from './Label.vue'// 把Input、Label组件挂载到 Form 组件上Form.Input = InputForm.Label = Labelexport default Form// 使用:import Form from './components/Form'
/

命名空间组件在另外一种场景中的使用,从单个文件中导入多个组件时:

// FormComponents/index.jsimport Input from './Input.vue'import Label from './Label.vue'export default {Input,Label,}// 使用import * as Form from './FormComponents'label

2.10. 状态驱动的动态 CSS

Vue3标签可以通过v-bind这一 CSS 函数将 CSS 的值关联到动态的组件状态上。

const theme = {color: 'red'}

hello

p {// 使用顶层绑定color: v-bind('theme.color');}

2.11. 指令

全局指令:

自定义指令:

import { ref } from 'vue'const total = ref(10)// 自定义指令// 必须以 小写字母v开头的小驼峰 的格式来命名本地自定义指令// 在模板中使用时,需要用中划线的格式表示,不可直接使用vMyDirectiveconst vMyDirective = {beforeMount: (el, binding, vnode) => {el.style.borderColor = 'red'},updated(el, binding, vnode) {if (el.value % 2 !== 0) {el.style.borderColor = 'blue'} else {el.style.borderColor = 'red'}},}const add = () => {total.value++}

导入的指令:

// 导入的指令同样需要满足命名规范import { directive as vClickOutside } from 'v-click-outside'

更多关于指令,见官方文档(https://v3.cn.vuejs.org/guide/custom-directive.html#%E7%AE%80%E4%BB%8B、https://v3.cn.vuejs.org/api/application-api.html#directive)。

2.12. Composition Api类型约束

import { ref, reactive, computed } from 'vue' type User = { name: stringage: number} // refconst msg1 = ref('')//会默认约束成 string 类型,因为ts类型推导const msg2 = ref('')//可以通过范型约束类型const user1 = ref({ name: 'tang', age: 18 })//范型约束const user2 = ref({} as User)// 类型断言 // reactiveconst obj = reactive({})const user3 = reactive({ name: 'tang', age: 18 })const user4 = reactive({} as User) // computedconst msg3 = computed(() => msg1.value)const user5 = computed(() => {return { name: 'tang', age: 18 }})