This article mainly explains the basic use of <script setup>
and TypeScript
.
<script setup>
what is it
<script setup>
composition api
is compile-time syntactic sugar for use in single-file components (SFCs) .
At the time of writing this article, version 3.2.26vue
was used .
1.1. Development history
Let's take a look vue3 <script setup>
at the development history:
Vue3
Supported in earlier (3.0.0-beta.21
before) versionscomposition api
, can only be used in component optionssetup
functions.
<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>
-
Experimental feature added in version 3.0.0 -beta.21 .
<script setup>
If you use it, you will be prompted that it<script setup>
is still in the stage of experimental features. -
The experimental state that was removed in version 3.2.0 has since then been officially declared to be in regular use and has become one of the stable features of the framework.
<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. Advantages
Advantages compared to component option setup
functions :<script setup>
- Less and more concise code, no need to use
return {}
exposed variables and methods, no need to actively register when using components; - Better
Typescript
support, using pureTypescript
declarationsprops
and throwing events, no longer asoption api
lame as in; - better runtime performance;
Of course, <script setup>
it also has its own shortcomings, such as the need to learn additional API
.
So <script setup>
how to use it? What are the points of use? How to combine with TypeScript?
2. Points of use
2.1. Tools
Vue3
Support for Single File Components (SFC) TS IDE
please use <script setup lang="ts"> + VSCode + Volar
.
Type checking uses vue-tsc
the command.
- VSCode : the best front-end
IDE
. - Volar : A plug-in that
Vue3
provides*.vue
support for code highlighting, syntax hints, etc. for single-file componentsVSCode
;Vue2
you may be usingVetur
a plug-in that needs to be disabledVetur
, downloadedVolar
, and enabled. - vue-tsc : Type checking and
dts
build command line tool.
2.2. Basic usage
Add setup
attributes to <script>
code blocks.
<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>
If you need to use it TypeScript
, lang
add the attribute to <script>
the code block and assign it a value 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>
The script in the block will be compiled into setup
the content of the component options function, which means it will be executed every time the component instance is created.
The top-level bindings of <script setup>
the declaration (variables, functions, and content introduced by import) will be automatically exposed to the template and used directly in the template.
<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>
Notice:
-
Each
*.vue
file can contain at most one<script>
block at a time (exclusive<script setup>
); -
Each
*.vue
file can contain at most one<script setup>
block at a time (excluding regular ones<script>
);
2.3. Compiler macros
Compiler macros (compiler macros) are: defineProps
, defineEmits
, withDefaults
, defineExpose
etc.
Compiler macros can only<script setup>
be used in blocks, do not need to be imported, and will be <script setup>
compiled out when the block is processed.
Compiler macros must be used at <script setup>
the top level of , and cannot be <script setup>
referenced in local variables of .
defineProps
<script setup>
There is no component configuration item in the block, that is to say, there is no option props
, which needs to be used defineProps
to declare props
related information. defineProps
The object received props
is the same as the value of the component option.
<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 version:
<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>
From the code, it can be found that there is no default value defined TS
in the writing method .props
Vue3
withDefaults
This compiler macro is provided for us to props
provide default values.
<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>
One caveat: declaring a props
variable with the same name as the property at the top level can be problematic.
<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>
So, as with component options, don't define props
top-level variables with the same name as the property.
defineEmits
Similarly, <script setup>
there is no component configuration item in the block emits
, and defineEmits
compiler macros need to be used to declare emits
related information.
// ./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>
Use components:
<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 version:
// ./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>
Use components:
<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
In Vue3
, any <script setup>
binding declared in is not exposed by default, that is, ref
the binding declared by the component instance cannot be obtained through the template.
Vue3
Compiler macros are provided defineExpose
to explicitly expose variables and methods declared in components that need to be exposed.
// ./components/HelloWorld.vue
<script setup>
import { ref } from 'vue'
const msg = ref('Hello Vue3')
const handleChangeMsg = (v) => {
msg.value = v
}
// 对外暴露的属性
defineExpose({
msg,
handleChangeMsg,
})
</script>
Use components:
<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 version:
// ./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>
Use components:
<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. Helper functions
<script setup>
The auxiliary functions commonly used in , hooks api
mainly include: useAttrs
, useSlots
, useCssModule
, and other auxiliary functions are still in the experimental stage, and will not be introduced.
useAttrs
Used in templates $attrs
to access attrs
data, Vue2
compared to , Vue3
which $attrs
also contains class
and style
attributes.
<script setup>
Use useAttrs
the function in to get the attrs
data.
<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
Used in templates $slots
to access slots
data.
<script setup>
Use useSlots
the function in to get the slots
slot data.
<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
In Vue3
, it is also supported CSS Modules
, <style>
adding module
attributes on , ie <style module>
.
<style module>
Code blocks are compiled to CSS Modules
and the generated CSS classes $style
are exposed to components as object keys, which can be used directly in templates $style
. As for <style module="content">
named CSS Modules
, the CSS class generated after compilation content
is exposed to the component as the key of the object, that is, module
whatever the property value is, the object will be exposed.
<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>
Note that for CSS Modules with the same name, the later ones will overwrite the former ones.
2.5. Using components
In component options, templates need to use components (except global components), which need to components
be registered in the options.
However, <script setup>
the component does not need to be registered again, and the template can be used directly, which is actually equivalent to a top-level variable.
It is recommended to use PascalCase to name components and use components.
<script setup>
import HelloWorld from './HelloWorld.vue'
</script>
<template>
<HelloWorld />
</template>
2.6. Component name
<script setup>
There is no component configuration item name
, you can use an ordinary one <script>
to configure it 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>
use:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
console.log(HelloWorld.name) // 'HelloWorld'
</script>
<template>
<HelloWorld />
</template>
Note: If you set lang
properties, be consistent <script setup>
with <script>
your needs.lang
2.7. inheritAttrs
inheritAttrs
Indicates whether to disable attribute inheritance, the default value is true
.
<script setup>
There is no component configuration item inheritAttrs, you can use an ordinary one <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. Top-level await support
<script setup>
You can use top-level await in . The resulting code will be compiled into async setup()
<script setup>
const userInfo = await fetch(`/api/post/getUserInfo`)
</script>
Note: async setup()
It must be Suspense
used in combination with . Suspense
It is still in the experimental stage, and its API may change at any time. It is recommended not to use it for the time being.
2.9. Namespace components
In vue3
, we can use dot syntax to use components mounted on an object.
// 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>
/ |
Use of namespace components in another scenario, when importing multiple components from a single file:
// 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. State-driven dynamic CSS
Vue3
The middle <style>
tag can v-bind
associate the CSS value with the dynamic component state through this CSS function.
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
// 使用顶层绑定
color: v-bind('theme.color');
}
</style>
2.11. Commands
Global directives:
<template> |
|
<div v-click-outside /> |
|
</template> |
Custom directive:
<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>
Imported directives:
<script setup>
// 导入的指令同样需要满足命名规范
import { directive as vClickOutside } from 'v-click-outside'
</script>
<template>
<div v-click-outside />
</template>
For more information about directives, see official documents ( 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 Type Constraints
<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>