前言
vue3.3.1对Typescript的使用提供了更友好的支持。如果公司已有的项目中vue使用的是3.2版本,可参考:Vue3.2.47+TS 使用指南
Vue3.3.1相对于Vue3.2.47在Typescript方面主要有两点变化:1. defineProps类型支持外部导入;2.defineEmits提供了更简便的声明方式
0. 前提准备
- 搭建项目
npm create vite@latest my-vue-app -- --template vue-ts
- 目前vite生成的Vue项目版本依旧为3.2.47,因此需手动升级下vue及相关插件的版本
yarn add vue@^3.3.1
yarn add vue-tsc@^1.6.5 -D
yarn add vite@^4.3.5 -D
yarn add @vitejs/plugin-vue@^4.2.0 -D
-
更新VsCode扩展 TypeScript Vue Plugin (Volar) 到 V1.6.5 版本
-
配置别名
(1)安装
yarn add @types/node -D
(2)修改vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
//这里进行配置别名
resolve: {
alias: {
"@": path.resolve("./src"), // @代替src
},
},
});
(3)修改tsconfig.json
{
"compilerOptions": {
//...
"baseUrl": ".", //查询的基础路径
"paths": { "@/*": ["src/*"] } //路径映射,配合别名使用
},
}
1. ref
传入一个泛型参数
- 值 ref
const initCode = ref('200'); //默认推导出string类型
//或者手动定义更复杂的类型
const initCode = ref<string | number>('200');
- 模板 ref
<template>
<div ref="el"></div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const el = ref<HTMLImageElement | null>(null);
</script>
- 组件 ref
<template>
<HelloWorld ref="helloworld" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import HelloWorld from '@/components/HelloWorld.vue';
const helloworld = ref<InstanceType<typeof HelloWorld> | null>(null);
onMounted(() => {
//调用子组件的handleClick方法
helloworld.value?.handleClick();
});
</script>
如果子组件使用<script setup lang="ts">
,默认是全关闭的,子组件需使用defineExpose定义父组件能访问的属性
2. reactive
- 定义接口
新建src/types/user.ts(在types文件夹下新建user.ts)
export interface User {
name: string;
age: number;
}
- 使用
<script setup lang="ts">
import { reactive } from 'vue';
import type { User } from '@/types/user';
//reactive 会隐式地从它的参数中推导类型
//也可以使用接口直接给变量定义类型
const user: User = reactive({
name: 'zhangsan',
age: 20,
});
</script>
提示:不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同
3. computed
computed() 会自动从其计算函数的返回值上推导出类型
也可以通过泛型参数显式指定类型
const age = computed<number>(() => {
return 2;
});
4. defineProps(父传子)
定义props类型,直接使用,不需要import引入
1. 基本使用
传入泛型参数
<script setup lang="ts">
//使用外部导入的类型
import type { User } from "@/types/user";
const props = defineProps<User>();
console.log(props.name);
</script>
2. 定义默认值
使用 withDefaults 定义默认值
<script setup lang="ts">
//使用外部导入的类型
import type { User } from "@/types/user";
const props = withDefaults(defineProps<User>(), {
name: "hello",
age: 10,
});
console.log(props.name);
</script>
5. defineEmits(子传父)
定义emits类型 ,直接使用,不需要import引入
父组件
<template>
<div>
<HelloWorld @change="handleChange" />
</div>
</template>
<script setup lang="ts">
import HelloWorld from "@/components/HelloWorld.vue";
import type { User } from "@/types/user";
const handleChange = (value: User) => {
console.log(value);
};
</script>
子组件
<template>
<div>
<button @click="handleClick">按钮</button>
</div>
</template>
<script setup lang="ts">
import type { User } from "@/types/user";
const emit = defineEmits<{ change: [value: User] }>();
const handleClick = () => {
emit("change", { name: "2", age: 21 });
};
</script>
6. defineExpose(父调用子)
定义父组件通过模板 ref 能获取到的属性
接着上面组件ref案例修改子组件HelloWorld
<template>
<div></div>
</template>
<script setup lang="ts">
const handleClick = () => {
console.log('子组件方法');
};
defineExpose({ handleClick });
</script>
7. provide / inject(跨组件传值)
1. key是Symbol
新建src/constant/index.ts
import type { InjectionKey } from 'vue';
export const key = Symbol() as InjectionKey<string>;
<!-- 父组件使用provide提供值 -->
<script setup lang="ts">
import { provide } from 'vue';
import { key } from '@/constant/index';
provide(key, '123'); //提供改变响应式对象的方法
</script>
<!-- 子组件使用inject取值 -->
<script setup lang="ts">
import { inject } from 'vue';
import { key } from '@/constant/index';
const string = inject(key);
</script>
2. key是字符串
inject返回的类型是 unknown,需要通过泛型参数显式声明
<!-- 父组件提供provide -->
<script setup lang="ts">
import { ref, provide } from 'vue';
const state = ref(0);
const handlerState = () => {
state.value = 1;
};
provide('info', state); //提供响应式对象
provide('func', handlerState); //提供改变响应式对象的方法
</script>
<!-- 子组件使用inject取值 -->
<script setup lang="ts">
import { inject } from 'vue';
//通过泛型参数显式声明
const state = inject<number>('info');
const func = inject<() => void>('func');
</script>
3. undefined问题
由于无法保证provide会提供这个值,因此inject通过泛型参数显示声明了类型,还会多个undefined类型
- 提供默认值,可消除undefined
const state = inject<number>('info', 20);
- 使用类型断言,告诉编辑器这个值一定会提供
const state = inject('info') as number;
8. 事件类型
1. input change事件
<template>
<input type="text" @change="handleChange" />
</template>
<script setup lang="ts">
const handleChange = (evt: Event) => {
console.log((evt.target as HTMLInputElement).value);
};
</script>
2. button Click事件
<template>
<button @click="handleClick">按钮</button>
</template>
<script setup lang="ts">
const handleClick = (evt: Event) => {
//获取按钮的样式信息
console.log((evt.target as HTMLButtonElement).style);
};
</script>
3. HTML标签映射关系
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"abbr": HTMLElement;
"address": HTMLElement;
"applet": HTMLAppletElement;
"area": HTMLAreaElement;
"article": HTMLElement;
"aside": HTMLElement;
"audio": HTMLAudioElement;
"b": HTMLElement;
"base": HTMLBaseElement;
"basefont": HTMLBaseFontElement;
"bdi": HTMLElement;
"bdo": HTMLElement;
"blockquote": HTMLQuoteElement;
"body": HTMLBodyElement;
"br": HTMLBRElement;
"button": HTMLButtonElement;
"canvas": HTMLCanvasElement;
"caption": HTMLTableCaptionElement;
"cite": HTMLElement;
"code": HTMLElement;
"col": HTMLTableColElement;
"colgroup": HTMLTableColElement;
"data": HTMLDataElement;
"datalist": HTMLDataListElement;
"dd": HTMLElement;
"del": HTMLModElement;
"details": HTMLDetailsElement;
"dfn": HTMLElement;
"dialog": HTMLDialogElement;
"dir": HTMLDirectoryElement;
"div": HTMLDivElement;
"dl": HTMLDListElement;
"dt": HTMLElement;
"em": HTMLElement;
"embed": HTMLEmbedElement;
"fieldset": HTMLFieldSetElement;
"figcaption": HTMLElement;
"figure": HTMLElement;
"font": HTMLFontElement;
"footer": HTMLElement;
"form": HTMLFormElement;
"frame": HTMLFrameElement;
"frameset": HTMLFrameSetElement;
"h1": HTMLHeadingElement;
"h2": HTMLHeadingElement;
"h3": HTMLHeadingElement;
"h4": HTMLHeadingElement;
"h5": HTMLHeadingElement;
"h6": HTMLHeadingElement;
"head": HTMLHeadElement;
"header": HTMLElement;
"hgroup": HTMLElement;
"hr": HTMLHRElement;
"html": HTMLHtmlElement;
"i": HTMLElement;
"iframe": HTMLIFrameElement;
"img": HTMLImageElement;
"input": HTMLInputElement;
"ins": HTMLModElement;
"kbd": HTMLElement;
"label": HTMLLabelElement;
"legend": HTMLLegendElement;
"li": HTMLLIElement;
"link": HTMLLinkElement;
"main": HTMLElement;
"map": HTMLMapElement;
"mark": HTMLElement;
"marquee": HTMLMarqueeElement;
"menu": HTMLMenuElement;
"meta": HTMLMetaElement;
"meter": HTMLMeterElement;
"nav": HTMLElement;
"noscript": HTMLElement;
"object": HTMLObjectElement;
"ol": HTMLOListElement;
"optgroup": HTMLOptGroupElement;
"option": HTMLOptionElement;
"output": HTMLOutputElement;
"p": HTMLParagraphElement;
"param": HTMLParamElement;
"picture": HTMLPictureElement;
"pre": HTMLPreElement;
"progress": HTMLProgressElement;
"q": HTMLQuoteElement;
"rp": HTMLElement;
"rt": HTMLElement;
"ruby": HTMLElement;
"s": HTMLElement;
"samp": HTMLElement;
"script": HTMLScriptElement;
"section": HTMLElement;
"select": HTMLSelectElement;
"slot": HTMLSlotElement;
"small": HTMLElement;
"source": HTMLSourceElement;
"span": HTMLSpanElement;
"strong": HTMLElement;
"style": HTMLStyleElement;
"sub": HTMLElement;
"summary": HTMLElement;
"sup": HTMLElement;
"table": HTMLTableElement;
"tbody": HTMLTableSectionElement;
"td": HTMLTableDataCellElement;
"template": HTMLTemplateElement;
"textarea": HTMLTextAreaElement;
"tfoot": HTMLTableSectionElement;
"th": HTMLTableHeaderCellElement;
"thead": HTMLTableSectionElement;
"time": HTMLTimeElement;
"title": HTMLTitleElement;
"tr": HTMLTableRowElement;
"track": HTMLTrackElement;
"u": HTMLElement;
"ul": HTMLUListElement;
"var": HTMLElement;
"video": HTMLVideoElement;
"wbr": HTMLElement;
}
9. 结尾
vue3.3还新增了defineModel(实验特性),defineSlots。这两个API因使用情景罕见,所以没有在本篇文章介绍,有兴趣的可自行去官网了解。
往期精彩回顾:
原文链接:https://juejin.cn/post/7233057834287710267 作者:敲代码的彭于晏