vue3 相比 vue2 对 ts 拥有更友好的支持,当初在用 vue2 写 ts 时各种装饰器,现在只要使用
<script setup lang="ts"></script>标签就可以直接使用 ts。
# 1. ref
传入一个泛型参数
# 1. 值 ref
| // 默认推导出 string 类型 | |
| const initCode = ref('200'); | |
| // 或者手动定义更复杂的类型 | |
| const initCode = ref<string | number>('200'); | 
# 2. 模板 ref
| <template> | |
| <div ref="el"></div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { ref } from 'vue'; | |
| const el = ref<HTMLImageElement | null>(null); | |
| </script> | 
# 3. 组件 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
# 1. 定义接口
新建 src/types/user.ts (在 types 文件夹下新建 user.ts)
| export interface User { | |
| name: string; | |
| age: number; | |
| } | 
# 2. 使用
| <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. 运行时声明
类型作为参数的一部分传入
# 1. 基本使用
运行时声明的类型共八大类:String,Number,Boolean,Array,Object,Date,Function,Symbol(注意这里和 ts 的类型有区别,例如 ts 中字符串类型为 string,这里是 String)
如果想为引用类型定义更具体的类型,需使用 PropType
| <template> | |
| <div> | |
| <div><!--swig0--></div> | |
| <div><!--swig1--></div> | |
| <div><!--swig2-->--<!--swig3--></div> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import { PropType } from 'vue'; | |
| interface User { | |
| name: string; | |
| age: number; | |
| } | |
| const props = defineProps({ | |
| msg: { | |
| type: String, | |
| default: 'hello', | |
| }, | |
| address: { | |
| type: [String, Number], // 联合类型,类似于 ts 的 | | |
| default: 2, | |
| }, | |
| user: { | |
| type: Object as PropType<User>, // 定义更具体的类型 | |
| default: () => ({ | |
| name: 'zhangsan', | |
| age: 12, | |
| }), | |
| }, | |
| }); | |
| console.log(props.msg); | |
| console.log(props.user.name); | |
| </script> | 
# 2. 接口 User 由外部传入
| <script setup lang="ts"> | |
| import type { User } from '@/types/user'; | |
| // 将原先定义的接口注释掉 | |
| // interface User { | |
| //   name: string; | |
| //   age: number; | |
| // } | |
| </script> | 
# 2. 类型声明(使用泛型)
# 1. 基本使用
使用泛型,不需要 PropType 定义更具体的类型
| <template> | |
| <div><!--swig4--></div> | |
| </template> | |
| <script setup lang="ts"> | |
| interface User { | |
| name?: string; | |
| age?: number; | |
| } | |
| const props = defineProps<User>(); | |
| console.log(props.name); | |
| </script> | 
# 2. 定义默认值
使用泛型,需要使用 withDefaults 定义默认值
| <script setup lang="ts"> | |
| interface User { | |
| name: string; | |
| age: number; | |
| } | |
| const props = withDefaults(defineProps<User>(), { | |
| name: 'hello', | |
| age: 10, | |
| }); | |
| </script> | 
# 3. 局限性
不能使用外部传入的类型(未来版本中可能会解决这个限制)
| import type { User } from '@/types/user'; | |
| // 将原先定义的接口注释掉 | |
| // interface User { | |
| //   name: string; | |
| //   age: number; | |
| // } | 
报错

# 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> | 
# 1. 运行时声明
| <!-- 子组件 --> | |
| <template> | |
| <div> | |
| <button @click="handleClick">按钮</button> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| const emit = defineEmits(['change']); | |
| const handleClick = () => { | |
| emit('change', { name: '2', age: 21 }); | |
| }; | |
| </script> | 
# 2. 类型声明(使用泛型)
可以对所触发事件的类型进行更细粒度的控制。并且可以使用外部传入的类型
| <!-- 子组件 --> | |
| <template> | |
| <div> | |
| <button @click="handleClick">按钮</button> | |
| </div> | |
| </template> | |
| <script setup lang="ts"> | |
| import type { User } from '@/types/user'; | |
| const emit = defineEmits<{ (e: 'change', value: User): void }>(); | |
| 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
| js | |
| 代码解读 | |
| 复制代码const state = inject<number>('info', 20); | 
- 使用类型断言,告诉编辑器这个值一定会提供
| js | |
| 代码解读 | |
| 复制代码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; | |
| } | 
