Vue3 <script setup lang=“ts“> 的基本使用
作者:汤玉鹏
本文主要是讲解 <script setup>
与 TypeScript
的基本使用。
1.<script setup> 是什么?
<script setup>
是在单文件组件 (SFC) 中使用 composition api
的编译时语法糖。
本文写作时,vue
使用的 3.2.26 版本。
1.1. 发展历程
我们先看看 vue3 <script setup>
的发展历程:
Vue3
在早期版本( 3.0.0-beta.21
之前)中对 composition api
的支持,只能在组件选项 setup
函数中使用。
<template> <h1>{{ msg }}</h1> <button type="button" @click="add">count is: {{ count }}</button> <ComponentA /> <ComponentB /> </template> <script> 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, } }, }) </script>
- 在 3.0.0-beta.21 版本中增加了
<script setup>
的实验特性。如果你使用了,会提示你<script setup>
还处在实验特性阶段。 - 在 3.2.0 版本中移除
<script setup>
的实验状态,从此,宣告<script setup>
正式转正使用,成为框架稳定的特性之一。
<script setup lang="ts"> import { ref } from 'vue' import ComponentA from '@/components/ComponentA' import ComponentB from '@/components/ComponentB' defineProps<{ msg: string }>() const count = ref(0) function add() { count.value++ } </script>x <template> <h1>{{ msg }}</h1> <button type="button" @click="add">count is: {{ count }}</button> <ComponentA /> <ComponentB /> </template>
1.2. 优势
与组件选项 setup
函数对比, <script setup>
的优点:
- 更少、更简洁的代码,不需要使用
return {}
暴露变量和方法了,使用组件时不需要主动注册了; - 更好的
Typescript
支持,使用纯Typescript
声明props
和抛出事件,不会再像option api
里那么蹩脚了; - 更好的运行时性能;
当然, <script setup>
也是有自己的缺点的,比如需要学习额外的 API
。
那么 <script setup>
怎么使用呢?有哪些使用要点?与TypeScript如何结合?
2. 使用要点
2.1. 工具
Vue3
单文件组件 (SFC) 的 TS IDE
支持请用 <script setup lang="ts"> + VSCode + Volar
。
类型检查使用 vue-tsc
命令。
VSCode:前端最好用的 IDE
。
Volar:为 Vue3
的 *.vue
单文件组件提供代码高亮、语法提示等功能支持的 VSCode
插件;Vue2
你可能是使用的 Vetur
插件,需要禁用 Vetur
,下载 Volar
,并启用它。
vue-tsc:类型检查和 dts
构建命令行工具。
2.2. 基本用法
将 setup
属性添加到 <script>
代码块上。
<script setup> import { ref } from 'vue' defineProps({ msg: String }) const count = ref(0) function add() { count.value++ } </script> <template> <h1>{{ msg }}</h1> <button type="button" @click="add">count is: {{ count }}</button> </template>
若需要使用 TypeScript
,则将 lang
属性添加到 <script>
代码块上,并赋值 ts
。
<script setup lang="ts"> import { ref } from 'vue' defineProps<{ msg: string }>() const count = ref(0) function add() { count.value++ } </script> <template> <h1>{{ msg }}</h1> <button type="button" @click="add">count is: {{ count }}</button> </template>
<script setup>
块中的脚本会被编译成组件选项 setup
函数的内容,也就是说它会在每次组件实例被创建的时候执行。
在 <script setup>
声明的顶层绑定(变量、函数、import引入的内容),都会自动暴露给模板,在模板中直接使用。
<script setup> 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++ } </script> <template> <h1>{{ msg }}</h1> <h1>{{ getToken() }}</h1> <button type="button" @click="add">count is: {{ count }}</button> <ComponentA /> </template>
注意:
- 每个
*.vue
文件最多可同时包含一个<script>
块 (不包括<script setup>
); - 每个
*.vue
文件最多可同时包含一个<script setup>
块 (不包括常规的<script>
);
2.3. 编译器宏
编译器宏(compiler macros) 有:defineProps
、defineEmits
、withDefaults
、defineExpose
等。
编译器宏只能在 <script setup>
块中使用,不需要被导入,并且会在处理 <script setup>
块时被一同编译掉。
编译器宏必须在 <script setup>
的顶层使用,不可以在 <script setup>
的局部变量中引用。
defineProps
在 <script setup>
块中是没有组件配置项的,也就是说是没有 props
选项,需要使用 defineProps
来声明 props
相关信息。defineProps
接收的对象和组件选项 props
的值一样。
<script setup> const props = defineProps({ msg: String, title: { type: String, default: '我是标题' }, list: { type: Array, default: () => [] } }) // 在 js 中使用 props 中的属性 console.log(props.msg) </script> <template> <!-- 在模板中直接使用 props 中声明的变量 --> <h1>{{ msg }}</h1> <div>{{ title }}</div> </template>
TS 版本:
<script setup lang="ts"> interface ListItem { name: string age: number } const props = defineProps<{ msg: string title: string list: ListItem[] }>() // 在 ts 中使用 props 中的属性,具有很好的类型推断能力 console.log(props.list[0].age) </script> <template> <h1>{{ msg }}</h1> <div>{{ title }}</div> </template>
从代码中可以发现 TS
写法里 props
没有定义默认值。
Vue3
为我们提供了 withDefaults
这个编译器宏,给 props
提供默认值。
<script setup lang="ts"> interface ListItem { name: string age: number } interface Props { msg: string // title可选 title?: string list: ListItem[] } // withDefaults 的第二个参数便是默认参数设置,会被编译为运行时 props 的 default 选项 const props = withDefaults(defineProps<Props>(), { title: '我是标题', // 对于array、object需要使用函数,和以前的写法一样 list: () => [] }) // 在 ts 中使用 props 中的属性,具有很好的类型推断能力 console.log(props.list[0].age) </script> <template> <h1>{{ msg }}</h1> <div>{{ title }}</div> </template>
一个需要注意的地方:在顶层声明一个和props
的属性同名的变量,会有些问题。
<script setup> const props = defineProps({ title: { type: String, default: '我是标题' } }) // 在顶层声明一个和props的属性title同名的变量 const title = '123' </script> <template> <!-- props.title 显示的是 props.title 的值,‘我是标题' --> <div>{{ props.title }}</div> <!-- title 显示的是 在顶层声明的 title 的值,‘123' --> <div>{{ title }}</div> </template>
所以,和组件选项一样,不要定义和 props
的属性同名的顶层变量。
defineEmits
一样的,在 <script setup>
块中也是没有组件配置项 emits
的,需要使用 defineEmits
编译器宏声明 emits
相关信息。
// ./components/HelloWorld.vue <script setup> defineProps({ msg: String, }) const emits = defineEmits(['changeMsg']) const handleChangeMsg = () => { emits('changeMsg', 'Hello TS') } </script> <template> <h1>{{ msg }}</h1> <button @click="handleChangeMsg">handleChangeMsg</button> </template>
使用组件:
<script setup> import { ref } from 'vue' import HelloWorld from './components/HelloWorld.vue' const msg = ref('Hello Vue3') const changeMsg = (v) => { msg.value = v } </script> <template> <HelloWorld :msg="msg" @changeMsg="changeMsg" /> </template>
TS 版本:
// ./components/HelloWorld.vue <script setup lang="ts"> defineProps<{ msg: string }>() const emits = defineEmits<{ (e: 'changeMsg', value: string): void }>() const handleChangeMsg = () => { emits('changeMsg', 'Hello TS') } </script> <template> <h1>{{ msg }}</h1> <button @click="handleChangeMsg">handleChangeMsg</button> </template>
使用组件:
<script setup lang="ts"> import { ref } from 'vue' import HelloWorld from './components/HelloWorld.vue' const msg = ref('Hello Vue3') const changeMsg = (v: string) => { msg.value = v } </script> <template> <HelloWorld :msg="msg" @changeMsg="changeMsg" /> </template>
defineExpose
在 Vue3
中,默认不会暴露任何在 <script setup>
中声明的绑定,即不能通过模板 ref
获取到组件实例声明的绑定。
Vue3
提供了 defineExpose
编译器宏,可以显式地暴露需要暴露的组件中声明的变量和方法。
// ./components/HelloWorld.vue <script setup> import { ref } from 'vue' const msg = ref('Hello Vue3') const handleChangeMsg = (v) => { msg.value = v } // 对外暴露的属性 defineExpose({ msg, handleChangeMsg, }) </script>
使用组件:
<script setup> 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') } </script> <template> <HelloWorld ref="root" /> <button @click="handleChangeMsg">handleChangeMsg</button> </template>
TS 版本:
// ./components/HelloWorld.vue <script setup lang="ts"> import { ref } from 'vue' const msg = ref('Hello Vue3') const handleChangeMsg = (v: string) => { msg.value = v } defineExpose({ msg, handleChangeMsg }) </script> <template> <h1>{{ msg }}</h1> </template>
使用组件:
<script setup lang="ts"> import { ref, onMounted } from 'vue' import HelloWorld from './components/HelloWorld.vue' // 此处暂时使用any,需要定义类型 const root = ref<any>(null) onMounted(() => { console.log(root.value.msg) }) const handleChangeMsg = () => { root.value.handleChangeMsg('Hello TS') } </script> <template> <HelloWorld ref="root" /> <button @click="handleChangeMsg">handleChangeMsg</button> </template>
2.4. 辅助函数
在 <script setup>
中常用的辅助函数hooks api
,主要有:useAttrs
、useSlots
、useCssModule
,其他的辅助函数还在实验阶段,不做介绍。
useAttrs
在模板中使用 $attrs
来访问 attrs
数据,与 Vue2
相比,Vue3
的 $attrs
还包含了 class
和 style
属性。
在 <script setup>
中使用 useAttrs
函数获取 attrs
数据。
<script setup> import HelloWorld from './components/HelloWorld.vue' </script> <template> <HelloWorld class="hello-word" title="我是标题" /> </template>
// ./components/HelloWorld.vue <script setup> import { useAttrs } from 'vue' const attrs = useAttrs() // js中使用 console.log(attrs.class) // hello-word console.log(attrs.title) // 我是标题 </script> <template> <!-- 在模板中使用 $attrs 访问属性 --> <div>{{ $attrs.title }}</div> </template>
useSlots
在模板中使用 $slots
来访问 slots
数据。
在 <script setup>
中使用 useSlots
函数获取 slots
插槽数据。
<script setup> import HelloWorld from './components/HelloWorld.vue' </script> <template> <HelloWorld> <div>默认插槽</div> <template v-slot:footer> <div>具名插槽footer</div> </template> </HelloWorld> </template>
<script setup> import { useSlots } from 'vue' const slots = useSlots() // 在js中访问插槽默认插槽default、具名插槽footer console.log(slots.default) console.log(slots.footer) </script> <template> <div> <!-- 在模板中使用插槽 --> <slot></slot> <slot name="footer"></slot> </div> </template>
useCssModule
在 Vue3
中,也是支持 CSS Modules
的,在 <style>
上增加 module
属性,即<style module>
。
<style module>
代码块会被编译为 CSS Modules
并且将生成的 CSS 类作为 $style
对象的键暴露给组件,可以直接在模板中使用 $style
。而对于如 <style module="content">
具名 CSS Modules
,编译后生成的 CSS 类作为 content
对象的键暴露给组件,即module
属性值什么,就暴露什么对象。
<script setup lang="ts"> import { useCssModule } from 'vue' // 不传递参数,获取<style module>代码块编译后的css类对象 const style = useCssModule() console.log(style.success) // 获取到的是success类名经过 hash 计算后的类名 // 传递参数content,获取<style module="content">代码块编译后的css类对象 const contentStyle = useCssModule('content') </script> <template> <div class="success">普通style red</div> <div :class="$style.success">默认CssModule pink</div> <div :class="style.success">默认CssModule pink</div> <div :class="contentStyle.success">具名CssModule blue</div> <div :class="content.success">具名CssModule blue</div> </template> <!-- 普通style --> <style> .success { color: red; } </style> <!-- 无值的css module --> <style module lang="less"> .success { color: pink; } </style> <!-- 具名的css module --> <style module="content" lang="less"> .success { color: blue; } </style>
注意,同名的CSS Module,后面的会覆盖前面的。
2.5. 使用组件
在组件选项中,模板需要使用组件(除了全局组件),需要在 components
选项中注册。
而在 <script setup>
中组件不需要再注册,模板可以直接使用,其实就是相当于一个顶层变量。
建议使用大驼峰(PascalCase)命名组件和使用组件。
<script setup> import HelloWorld from './HelloWorld.vue' </script> <template> <HelloWorld /> </template>
2.6. 组件name
<script setup>
是没有组件配置项 name
的,可以再使用一个普通的 <script>
来配置 name
。
// ./components/HelloWorld.vue <script> export default { name: 'HelloWorld' } </script> <script setup> import { ref } from 'vue' const total = ref(10) </script> <template> <div>{{ total }}</div> </template>
使用:
<script setup> import HelloWorld from './components/HelloWorld.vue' console.log(HelloWorld.name) // 'HelloWorld' </script> <template> <HelloWorld /> </template>
注意: 如果你设置了 lang
属性,<script setup>
和 <script>
的 lang
需要保持一致。
2.7. inheritAttrs
inheritAttrs
表示是否禁用属性继承,默认值是 true
。
<script setup>
是没有组件配置项 inheritAttrs 的,可以再使用一个普通的 <script>
。
<script setup> import HelloWorld from './components/HelloWorld.vue' </script> <template> <HelloWorld title="我是title"/> </template>
./components/HelloWorld.vue
<script> export default { name: 'HelloWorld', inheritAttrs: false, } </script> <script setup> import { useAttrs } from 'vue' const attrs = useAttrs() </script> <template> <div> <span :title="attrs.title">hover一下看title</span> <span :title="$attrs.title">hover一下看title</span> </div> </template>
2.8. 顶层await支持
<script setup>
中可以使用顶层 await。结果代码会被编译成 async setup()
<script setup> const userInfo = await fetch(`/api/post/getUserInfo`) </script>
注意:async setup()
必须与 Suspense
组合使用,Suspense
目前还是处于实验阶段的特性,其 API 可能随时会发生变动,建议暂时不要使用。
2.9. 命名空间组件
在 vue3
中,我们可以使用点语法来使用挂载在一个对象上的组件。
// components/Form/index.js import Form from './Form.vue' import Input from './Input.vue' import Label from './Label.vue' // 把Input、Label组件挂载到 Form 组件上 Form.Input = Input Form.Label = Label export default Form // 使用: <script setup lang="ts"> import Form from './components/Form' </script> <template> <Form> <Form.Label /> <Form.Input /> </Form> </template>
命名空间组件在另外一种场景中的使用,从单个文件中导入多个组件时:
// FormComponents/index.js import Input from './Input.vue' import Label from './Label.vue' export default { Input, Label, } // 使用 <script setup> import * as Form from './FormComponents' </script> <template> <Form.Input> <Form.Label>label</Form.Label> </Form.Input> </template>
2.10. 状态驱动的动态 CSS
Vue3
中 <style>
标签可以通过 v-bind
这一 CSS 函数将 CSS 的值关联到动态的组件状态上。
<script setup> const theme = { color: 'red' } </script> <template> <p>hello</p> </template> <style scoped> p { // 使用顶层绑定 color: v-bind('theme.color'); } </style>
2.11. 指令
全局指令:
<template> | |
<div v-click-outside /> | |
</template> |
自定义指令:
<script setup> import { ref } from 'vue' const total = ref(10) // 自定义指令 // 必须以 小写字母v开头的小驼峰 的格式来命名本地自定义指令 // 在模板中使用时,需要用中划线的格式表示,不可直接使用vMyDirective const 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++ } </script> <template> <input :value="total" v-my-directive /> <button @click="add">add+1</button> </template>
导入的指令:
<script setup> // 导入的指令同样需要满足命名规范 import { directive as vClickOutside } from 'v-click-outside' </script> <template> <div v-click-outside /> </template>
更多关于指令,见官方文档(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类型约束
<script setup lang="ts"> import { ref, reactive, computed } from 'vue' type User = { name: string age: number } // ref const msg1 = ref('') // 会默认约束成 string 类型,因为ts类型推导 const msg2 = ref<string>('') // 可以通过范型约束类型 const user1 = ref<User>({ name: 'tang', age: 18 }) // 范型约束 const user2 = ref({} as User) // 类型断言 // reactive const obj = reactive({}) const user3 = reactive<User>({ name: 'tang', age: 18 }) const user4 = reactive({} as User) // computed const msg3 = computed(() => msg1.value) const user5 = computed<User>(() => { return { name: 'tang', age: 18 } }) </script>
到此这篇关于Vue3 <script setup lang=“ts“> 使用指南的文章就介绍到这了,更多相关Vue3 <script setup lang=“ts“>内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!