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; | |
} |