The 101 guide to Script Setup in Vue 3

With the release of the Composition API, the new component option setup has also made its way into Vue. With this, we no longer need other options in most cases and write all our code within this function. The idea of <script setup is that you want to get rid of the unnecessary wrapper and the other old component options so you can write components simpler and focused.

<script lang="ts">
import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const count = ref<number>(0)

    function increment() {
      count.value++
    }

    return {
      count,
      increment
    }
  }
})
</script>
<script setup lang="ts">
import { ref } from 'vue'

const count = ref<number>(0)

function increment() {
  count.value++
}
</script>

With the <script setup> syntax we can write our component logic more compactly. Even a return statement is no longer needed. Every variable and method we define is automatically provided in the template.

We can also use imported data like functions and even components directly. Components don`t even have to be registered separately anymore:

<script setup lang="ts">
import { ref } from 'vue'
import { format } from '@/utils/currency'
import MyComponent from '@/components/MyComponent.vue'

const count = ref<number>(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ format(count) }}</button>
  <MyComponent />
</template>

# Props and Events

To declare options like props and emits we have to use so called compiler macros which are automatically available inside <script setup>.

<script setup lang="ts">
const props = defineProps({
  msg: String
})

const emit = defineEmits(['update', 'remove'])
</script>

The two functions do not need to be imported and are compiled away when <script setup> is preprocessed.

defineProps takes the same value as the props option, while defineEmits takes the same value as the emits option.

If we are already using TypeScript in our application anyway, then we can declare props and emits with a plain type syntax as well:

const props = defineProps<{
  msg: string
  count?: number
}>()

const emit = defineEmits<{
  (e: 'update', id: number): void
  (e: 'remove'): void
}>()

Both approaches cannot be combined, i.e. either we use the generic or the function brackets syntax, but never both at the same time. If we decide to go the TypeScript way, it is important to mention that we cannot currently use imported types or interfaces, but only type literals (see example) or types/interfaces defined in the same file.

In the case of events, we can specify the parameters more precisely ourselves and thus write type-safe code.

# Default values for props

Above in the example we have marked the prop count as optional, but here we lack a possibility to specify a default value. For this reason there is another compiler macro called withDefaults.

interface Props {
  msg: string
  count?: number
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
})

If withDefaults is not used, but a prop is marked as optional in the type, Vue treats this prop as required.

# Using Slots and Attributes

Normally, slots and attributes in components are used directly in the template via $slots and $attrs and rarely if ever needed in script. If it should be necessary nevertheless, Vue provides appropriate functions with useSlots and useAttrs.

<script setup lang="ts">
import { useSlots, useAttrs } from 'vue'

const slots = useSlots()
const attrs = useAttrs()
</script>

# Use additional options

In some cases it is necessary that we declare custom options in our components. Vuelidate's validations option is just one popular example. These other options, which include setting a component name via name, are not possible with <script setup>.

The solution to these problems is to use an additional script tag. The two blocks are then automatically merged when the *.vue file is compiled.

<script>
// executed only once
export const componentName = 'MyComponent';

export default {
	name: componentName
  inheritAttrs: false,
  customOptions: {}
}
</script>

<script setup>
// executed for each component instance
</script>

# Access from the outside?

If we use <script setup> every variable and method we define is automatically provided in the template, but the component itself is closed to the outside. So we cannot access the properties and functions declared inside <script setup> via the instance of a component, for example when using $parent or template refs.

To explicitly expose something, we need to use another compiler macro: defineExpose.

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

const count = ref<number>(0)

function increment() {
  count.value++
}

defineExpose({
  count,
  increment
})
</script>

# Top-level await

In components we often need to request data asynchronously from a service. In <script setup> we have the await keyword available for this at the top level.

A component defined in this way must be used with the suspense component so that Vue can take care of resolving the asynchrony and load the component properly.

<script setup>
const user = await fetch(`/users/1`).then((d) => d.json())
</script>
<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Warning: Suspense is currently still an experimental feature, which is not recommended for productive applications.

# Availability outside of Vue 3

If you are not lucky enough to be working with Vue 3 already, you will be happy to know that there is a fantastic plugin that makes <script setup> available in Vue 2 as well as Nuxt, Vite or the Vue CLI: https://github.com/antfu/unplugin-vue2-script-setup

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

The new Provide and Inject in Vue 3

Getting stuck into the prop drilling? Learn how provide/inject can make your components more flexible and independent in this short tutorial.

Anthony Konstantinidis

Anthony Konstantinidis

Jul 18, 2022

Going 3D with Trois.js and Vue 3

Learn about Trois.js, a JS library to render 3D scenes in Vue. In this article, we're learning the basics of using Trois.js in a Vite + Vue 3 app

Alvaro Saburido Rodriguez

Alvaro Saburido Rodriguez

Nov 16, 2021

Use Composition API to easily handle API requests in Vue.js

Not sure how to organize your API client in Vue.js? Learn this simple technique on how to do it using the new Composition API and make it easy.

Carlos Rodrigues

Carlos Rodrigues

Jul 6, 2020

Sponsors

VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Silver
Learning Partner