Vue.js 3 Feature: setup script

Setup script is a compile-time syntactic sugar for using Composition API inside SFCs. It is the recommended syntax if you are using both SFCs and Composition API. It provides a number of advantages over the normal script syntax. And it have a good support on TypeScript.

<script setup> is a compile-time syntactic sugar for using Composition API inside SFCs. It is the recommended syntax if you are using both SFCs and Composition API. It provides a number of advantages over the normal <script>syntax. And it have a good support on TypeScript.

Top-level bindings are exposed to template

When using <script setup>, any top-level bindings (::variables, function declarations, and imports::) declared inside <script setup> are directly usable in the template:

<script setup>
// imports node_modules, helper functions, and components
import { ref } from 'vue'
import { capitalize } from './helpers'
import MyComponent from './MyComponent.vue'
import * as Form from './form-components'

// variables
const msg = ref('Hello!')

// functions
const log = () => {
	console.log(msg)
}
</script>

<template>
  <div @click="log">{{ msg }}</div>
  <Form.Input>
    <Form.Label>label</Form.Label>
  </Form.Input>
</template>

defineProps & defineEmits

To declare options like props and emits with full type inference support, we can use the defineProps and defineEmits APIs, which are automatically available inside <script setup>:

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script>
  • defineProps accepts the same value as the props option, while defineEmitsaccepts the same value as the emits option.
  • defineProps and defineEmits are compiler macros only usable inside <script setup>. ::They do not need to be imported::, and are compiled away when <script setup> is processed.

Props and emits can also be declared using ::pure-type(TypeScript):: syntax by passing a literal type argument to defineProps or defineEmits:

const props = defineProps<{
  foo: string
  bar?: number
}>()

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

defineExpose

Components using <script setup> are closed by default - i.e. the public instance of the component, which is retrieved via template refs or $parent chains, will not expose any of the bindings declared inside <script setup>.

To explicitly expose properties in a <script setup> component, use the defineExpose compiler macro:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

When a parent gets an instance of this component via ::template refs::, the retrieved instance will be of the shape { a: number, b: number } (refs are automatically unwrapped just like on normal instances).

Usage alongside normal <script>

<script setup> can be used alongside normal <script>. A normal <script> may be needed in cases where we need to:

  • Declare options that cannot be expressed in <script setup>, for example inheritAttrs or custom options enabled via plugins.
  • ::Declaring named exports::.
  • Run side effects or create objects that should only execute once.
<script>
// normal <script>, executed in module scope (only once)
runSideEffectOnce()

// declare additional options
export default {
  inheritAttrs: false,
  customOptions: {}
}
</script>

<script setup>
// executed in setup() scope (for each instance)
</script>

Default props values when using type declaration

One drawback of the type-only defineProps declaration is that it doesn't have a way to provide default values for the props. To resolve this problem, a withDefaultscompiler macro is also provided:

interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

This will be compiled to equivalent runtime props default options. In addition, the withDefaults helper provides type checks for the default values, and ensures the returned props type has the optional flags removed for properties that do have default values declared.