Vue3 script setup user guide

preface

This article mainly explains the basic use of < script setup > and TypeScript.

What is < script setup >?

< script setup > is a compile time syntax for using the composition api in a single file component (SFC).

When writing this article, vue uses version 3.2.26.

1. Development course

Let's take a look at the development of vue3 < script setup >:

  • Vue3 supports the composition api in earlier versions (before 3.0.0-beta.21), which can only be used in the component option setup function.
<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++
    }
    // Use return {} to expose variables and methods to the template
    return {
      count,
      add,
    }
  },
})
</script>
  • In 3.0.0-beta The experimental feature of < script setup > has been added in version 21. If you use it, you will be prompted that < script setup > is still in the experimental feature stage.
  • In version 3.2.0, the experimental state of < script setup > was removed. From then on, it was announced that < script setup > was officially transferred to regular use, which has become one of the stable features of the framework.
<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>

<template>
  <h1>{{ msg }}</h1>
  <button type="button" @click="add">count is: {{ count }}</button>
  <ComponentA />
  <ComponentB />
</template>

2. Advantage

Compared with the component option setup function, < script setup > has the following advantages:

  • Less and more concise code, no need to use return {} to expose variables and methods, and no need to actively register when using components;
  • Better Typescript support. Using pure Typescript to declare props and throw events will no longer be as bad as in the option api;
  • Better runtime performance;

Of course, < script setup > also has its own shortcomings, such as the need to learn additional API s.

How to use < script setup >? What are the key points? How does it work with TypeScript?

3. Tools

For TS IDE support of Vue3 single file component (SFC), please use < script setup lang = "ts" > + vscode + Vol.

Type checking uses the Vue TSC command.

  • VSCode: the best IDE for the front end.
  • Vol: *. For Vue3 Vue single file component provides VSCode plug-in with code highlighting, syntax prompt and other functions; Vue2 you may be using the Vetur plug-in. You need to disable Vetur, Download Volar and enable it.
  • Vue TSC: type checking and dts build command line tools.

4. Basic usage

Add the setup attribute to the < script > code block.

<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 TypeScript is required, add the lang attribute to the < script > code block and assign the 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>

The script in the < script setup > block will be compiled into the contents of the component option setup function, that is, it will be executed every time the component instance is created.

The top-level bindings declared in < script setup > (variables, functions and contents introduced by import) will be automatically exposed to the template and used directly in the template.

<script setup>
import { ref } from 'vue'
// Externally introduced methods do not need to be exposed through the methods option. Templates can be used directly
import { getToken } from './utils'
// Externally introduced components do not need to be exposed through the components option. Templates can be used directly
import ComponentA from '@/components/ComponentA'

defineProps({
  msg: String
})
// Variable declaration, the template can be used directly
const count = ref(0)
// Function declaration, templates can be used directly
function add() {
  count.value++
}
</script>

<template>
  <h1>{{ msg }}</h1>
  <h1>{{ getToken() }}</h1>
  <button type="button" @click="add">count is: {{ count }}</button>
  <ComponentA />
</template>

be careful:

  • Each * vue files can contain at most one < script > block (excluding < script setup >);
  • Each * vue files can contain at most one < script setup > block at the same time (excluding conventional < script >);

5. Compiler macro

compiler macros include: defineProps, defineEmits, withDefaults, defineExpose, etc.

Compiler macros can only be used in < script setup > blocks, do not need to be imported, and will be compiled together when processing < script setup > blocks.

Compiler macros must be used at the top level of < script setup >, and cannot be referenced in local variables of < script setup >.

5.1 defineProps

There is no component configuration item in the < script setup > block, that is, there is no props option. You need to use defineProps to declare props related information. The object received by defineProps is the same as the value of component option props.

<script setup>
const props = defineProps({
  msg: String,
  title: {
    type: String,
    default: 'I'm the title'
  },
  list: {
    type: Array,
    default: () => []
  }
})

// Using properties in props in js
console.log(props.msg)
</script>

<template>
  <!-- Use directly in the template props Variables declared in -->
  <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[]
}>()

// Using the properties in props in ts has good type inference ability
console.log(props.list[0].age)
</script>

<template>
  <h1>{{ msg }}</h1>
  <div>{{ title }}</div>
</template>

It can be found from the code that props has no default value defined in TS writing method.

Vue3 provides us with the compiler macro withDefaults, which provides default values for props.

<script setup lang="ts">
interface ListItem {
  name: string
  age: number
}
interface Props {
  msg: string
  // title optional
  title?: string
  list: ListItem[]
}
// The second parameter of withDefaults is the default parameter setting, which will be compiled as the default option of props at runtime
const props = withDefaults(defineProps<Props>(), {
  title: 'I'm the title',
  // For array and object, you need to use functions, which is the same as before
  list: () => []
})
// Using the properties in props in ts has good type inference ability
console.log(props.list[0].age)
</script>

<template>
  <h1>{{ msg }}</h1>
  <div>{{ title }}</div>
</template>

One thing to pay attention to: there will be some problems when declaring a variable with the same name as the props attribute at the top level.

<script setup>
const props = defineProps({
  title: {
    type: String,
    default: 'I'm the title'
  }
})
// Declare a variable with the same name as the property title of props at the top level
const title = '123'
</script>

<template>
  <!-- props.title It shows props.title The value of,'I'm the title' -->
  <div>{{ props.title }}</div>
  <!-- title The display is declared at the top level title The value of,'123' -->
  <div>{{ title }}</div>
</template>

Therefore, as with component options, do not define top-level variables with the same name as props attributes.

5.2 defineEmits

Similarly, there is no component configuration item emits in the < script setup > block. You need to use the defineEmits compiler macro 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>

Using 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>

Using 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>

5.3 defineExpose

In Vue3, by default, no binding declared in < script setup > will be exposed, that is, the binding declared by the component instance cannot be obtained through the template ref.

Vue3 provides a defineExpose compiler macro that can 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
}
// Attributes of external exposure
defineExpose({
  msg,
  handleChangeMsg,
})
</script>

<template>
  <h1>{{ msg }}</h1>
</template>

Using 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 is temporarily used here, and the type needs to be defined
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>

6. Auxiliary function

The commonly used auxiliary functions hooks api in < script setup > mainly include: useAttrs, useSlots and useCssModule. Other auxiliary functions are still in the experimental stage and will not be introduced.

6.1 useAttrs

Attrs is used in the template to access attrs data. Compared with Vue2, Vue3's attrs also contains class and style attributes.

Use the useAttrs function in < script setup > to obtain attrs data.

<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld class="hello-word" title="I'm the 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)  // I'm the title
</script>

<template>
  <!-- Use in template $attrs Access properties -->
  <div>{{ $attrs.title }}</div>
</template>

6.2 useSlots

Use $slots in the template to access slots data.

Use useSlots function in < script setup > to obtain slots slot data.

<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld>
    <div>Default slot</div>
    <template v-slot:footer>
      <div>Named slot footer</div>
    </template>
  </HelloWorld>
</template>
<script setup>
import { useSlots } from 'vue'

const slots = useSlots()
// Access the default slot, default slot and named slot footer in js
console.log(slots.default)
console.log(slots.footer)
</script>

<template>
  <div>
    <!-- Using slots in templates -->
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>

6.3 useCssModule

In CSS, the < modules > attribute is also added in < CSS > module.

The < style module > code block will be compiled into CSS Modules and the generated CSS class will be used as

<script setup lang="ts">
import { useCssModule } from 'vue'

// Without passing parameters, get the css class object compiled by the < style module > code block
const style = useCssModule()
console.log(style.success)  // The obtained class name is the class name after hash calculation of success class name
    
// Pass the parameter content and get the css class object compiled by the < style module = "content" > code block
const contentStyle = useCssModule('content')
</script>

<template>
  <div class="success">ordinary style red</div>

  <div :class="$style.success">default CssModule pink</div>
  <div :class="style.success">default CssModule pink</div>

  <div :class="contentStyle.success">Named CssModule blue</div>
  <div :class="content.success">Named CssModule blue</div>
</template>

<!-- ordinary style -->
<style>
.success {
  color: red;
}
</style>

<!-- Valueless css module -->
<style module lang="less">
.success {
  color: pink;
}
</style>

<!-- Named css module -->
<style module="content" lang="less">
.success {
  color: blue;
}
</style>

Note that the CSS Module with the same name will overwrite the previous one.

7. Using components

In the component option, the template needs to use components (except global components) and need to be registered in the components option.

In < script setup >, the component does not need to be registered, and the template can be used directly. In fact, it is equivalent to a top-level variable.

It is recommended to use PascalCase to name and use components.

<script setup>
import HelloWorld from './HelloWorld.vue'
</script>

<template>
  <HelloWorld />
</template>

8. Component name

< script setup > has no component configuration item name. You can use an ordinary < script > to configure 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 the lang attribute, the Lang of < script setup > and < script > should be consistent.

9,inheritAttrs

inheritAttrs indicates whether to disable attribute inheritance. The default value is true.

< script setup > has no component configuration item inheritAttrs, so you can use another ordinary < script >.

<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld title="I am 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 Take a look title</span>
    <span :title="$attrs.title">hover Take a look title</span>
  </div>
</template>

10. Top level await support

Top level await can be used in < script setup >. The resulting code is compiled into async setup()

<script setup>
const userInfo = await fetch(`/api/post/getUserInfo`)
</script>

Note: async setup() must be used in combination with suspend. Suspend 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.

11. Namespace component

In vue3, we can use point 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'
// Mount the Input and Label components on the Form component
Form.Input = Input
Form.Label = Label

export default Form
// use:
<script setup lang="ts">
import Form from './components/Form'
</script>

<template>
  <Form>
    <Form.Label />
    <Form.Input />
  </Form>
</template>

The 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,
}
// use
<script setup>
import * as Form from './FormComponents'
</script>

<template>
  <Form.Input>
    <Form.Label>label</Form.Label>
  </Form.Input>
</template>

12. State driven dynamic css

The < style > tag in Vue3 can associate the CSS value to the dynamic component state through the CSS function v-bind.

<script setup>
const theme = {
  color: 'red'
}
</script>

<template>
  <p>hello</p>
</template>

<style scoped>
p {
  // Use top-level binding
  color: v-bind('theme.color');
}
</style>

13. Instruction

Global instruction:

<template>
  <div v-click-outside />
</template>

Custom Directive:

<script setup>
import { ref } from 'vue'
const total = ref(10)

// Custom instruction
// Local custom instructions must be named in the form of a small hump starting with the lowercase letter v
// When it is used in the template, it needs to be expressed in the format of middle dash. vMyDirective cannot be used directly
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 instructions:

<script setup>
// The imported instructions also need to meet the naming convention
import { directive as vClickOutside } from 'v-click-outside'
</script>

<template>
  <div v-click-outside />
</template>

For more instructions, see the official documentation

14. Composition Api type constraint

<script setup lang="ts">
import { ref, reactive, computed } from 'vue'

type User = { 
  name: string
  age: number
}

// ref
const msg1 = ref('')  //  The default constraint is string, because the ts type is derived
const msg2 = ref<string>('')  //  You can use the canonical constraint type
const user1 = ref<User>({ name: 'tang', age: 18 })  //  Norm constraint
const user2 = ref({} as User)  // Type Asserts 

// 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>

summary

This syntax has many features that make a single file component simpler! Just add a setup attribute to the script tag, and the whole script will directly become a setup function. All top-level variables and functions will be automatically exposed to the template for use (there is no need to return again), and the development efficiency will be greatly improved!

Even Youda called on everyone on his microblog: "if you can use Vue3 but still use the Options API, now with < script setup >, there is no reason not to change the Composition API."

Added by $var on Sat, 19 Feb 2022 13:37:25 +0200