expose() function limits properties exposed to parent component
expose() is a function that can be used to explicitly limit the properties exposed when the component instance is accessed by a parent component via template refs. Calling expose() with no arguments makes the instance closed and does not expose anything to the parent. Calling expose() with an object selectively exposes specified properties.
setup() hook entry point for Composition API
The setup() hook serves as the entry point for Composition API usage in components in two cases: when using Composition API without a build step, or when integrating with Composition-API-based code in an Options API component. For Single-File Components using Composition API, <script setup> is strongly recommended for more succinct and ergonomic syntax.
setup() return object exposes reactive state to template and component instance
Properties returned from setup() are made available both to the template and to the component instance via other Options API hooks. Refs returned from setup() are automatically shallow unwrapped when accessed in the template and on this, so you do not need to use .value when accessing them.
setup() this context and async restrictions
setup() does not have access to the component instance; this will have a value of undefined inside setup(). You can access Composition-API-exposed values from Options API, but not the other way around. setup() should return an object synchronously. The only case when async setup() can be used is when the component is a descendant of a Suspense component.
setup() props argument is reactive
The first argument in the setup function is the props argument. Props inside setup() are reactive and will be updated when new props are passed in.
Destructuring props loses reactivity
If you destructure the props object, the destructured variables will lose reactivity. It is therefore recommended to always access props in the form of props.xxx. To destructure props while retaining reactivity, use toRefs() to turn props into an object of refs before destructuring, or use toRef() to turn a single property into a ref.
setup() context object - second argument
The second argument passed to setup() is a Setup Context object. The context object exposes: attrs (non-reactive object, equivalent to $attrs), slots (non-reactive object, equivalent to $slots), emit (function, equivalent to $emit), and expose (function). The context object is not reactive and can be safely destructured.
attrs and slots are stateful but not reactive
attrs and slots are stateful objects that are always updated when the component itself is updated. This means you should avoid destructuring them and always reference properties as attrs.x or slots.x. Unlike props, the properties of attrs and slots are not reactive. If you intend to apply side effects based on changes to attrs or slots, you should do so inside an onBeforeUpdate lifecycle hook.
setup() can return a render function
setup() can return a render function which can directly make use of the reactive state declared in the same scope. Returning a render function prevents returning anything else. If you need to expose methods via template refs while returning a render function, you must call expose() to expose those methods.
setup() basic usage example
Example showing setup() with reactive state returned to template and accessed in Options API hooks:
```vue
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
// expose to template and other options API hooks
return {
count
}
},
mounted() {
console.log(this.count) // 0
}
}
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
```
This example demonstrates accessing reactive state in the template without .value and accessing it in Options API hooks via this.
Accessing props in setup() with toRefs example
Example showing how to destructure props while retaining reactivity using toRefs() and toRef():
```js
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// turn `props` into an object of refs, then destructure
const { title } = toRefs(props)
// `title` is a ref that tracks `props.title`
console.log(title.value)
// OR, turn a single property on `props` into a ref
const title = toRef(props, 'title')
}
}
```
This shows two approaches to destructuring props while maintaining reactivity.
setup() context object destructuring example
Example showing how to destructure the context object:
```js
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
```
The context object can be safely destructured since it is not reactive.
expose() in setup example
Example showing expose() usage to selectively expose properties:
```js
export default {
setup(props, { expose }) {
// make the instance "closed" -
// i.e. do not expose anything to the parent
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// selectively expose local state
expose({ count: publicCount })
}
}
```
This demonstrates calling expose() with no arguments to close the instance, then later calling it with an object to selectively expose specific properties.
setup() with render function example
Example showing setup() returning a render function:
```js
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
```
This demonstrates a render function directly using reactive state from setup().
setup() with render function and expose() example
Example showing how to expose methods when returning a render function from setup():
```js
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
```
The increment method becomes available in the parent component via a template ref.
Script setup basic syntax and behavior
To use script setup, add the setup attribute to the <script> block. Code inside <script setup> is compiled as the content of the component's setup() function. Unlike normal <script> which executes once when the component is first imported, code inside <script setup> executes every time an instance of the component is created.
Script setup top-level bindings exposed to template
Any top-level bindings declared inside <script setup>, including variables, function declarations, and imports, are directly usable in the template without needing to expose them via a methods option.
Refs automatically unwrapped in script setup templates
In <script setup>, reactive state needs to be explicitly created using Reactivity APIs. Refs are automatically unwrapped when referenced in templates, just like values returned from a setup() function.
Script setup component references as variables
In <script setup>, imported components are referenced as variables and can be used directly as custom component tag names in templates. PascalCase component tags are strongly recommended for consistency and to help differentiate from native custom elements.
Dynamic components in script setup
Since components in <script setup> are referenced as variables instead of registered under string keys, use dynamic :is binding when using dynamic components: <component :is="someCondition ? Foo : Bar" />. Components can be used as variables in ternary expressions.
Recursive components in script setup
An SFC can implicitly refer to itself via its filename. A file named FooBar.vue can refer to itself as <FooBar/> in its template. This has lower priority than imported components. If a named import conflicts with the component's inferred name, alias the import.
Namespaced components in script setup
Component tags with dots like <Foo.Bar> can be used to refer to components nested under object properties. This is useful when importing multiple components from a single file using import * as Form from './form-components'.
Custom directives in script setup naming convention
Local custom directives in <script setup> do not need to be explicitly registered, but must follow the naming scheme vNameOfDirective. Globally registered custom directives work normally. Imported directives can be renamed to fit the required naming scheme.
defineProps compiler macro
defineProps is a compiler macro automatically available inside <script setup> used to declare props with full type inference support. It does not need to be imported and is compiled away when <script setup> is processed. It accepts the same value as the props option. The options passed to defineProps will be hoisted out of setup into module scope, so they cannot reference local variables declared in setup scope but can reference imported bindings.
defineEmits compiler macro
defineEmits is a compiler macro automatically available inside <script setup> used to declare emitted events with full type inference support. It does not need to be imported and is compiled away when <script setup> is processed. It accepts the same value as the emits option. The options passed to defineEmits will be hoisted out of setup into module scope, so they cannot reference local variables declared in setup scope but can reference imported bindings.
Type-only props declaration syntax
Props can be declared using pure-type syntax by passing a literal type argument to defineProps: const props = defineProps<{ foo: string; bar?: number }>(). When using type declaration, the equivalent runtime declaration is automatically generated from static analysis. In dev mode, the compiler infers corresponding runtime validation from types. In prod mode, the compiler generates array format declaration to reduce bundle size.
Type-only emits declaration syntax
Emits can be declared using pure-type syntax by passing a literal type argument to defineEmits: const emit = defineEmits<{ (e: 'change', id: number): void; (e: 'update', value: string): void }>() or using the alternative syntax: const emit = defineEmits<{ change: [id: number]; update: [value: string] }>(). When using type declaration, the equivalent runtime declaration is automatically generated from static analysis.
Runtime and type declaration mutual exclusion
defineProps or defineEmits can only use either runtime declaration OR type declaration. Using both at the same time will result in a compile error.
Reactive props destructure in Vue 3.5+
In Vue 3.5 and above, variables destructured from the return value of defineProps are reactive. Vue's compiler automatically prepends props. when code in the same <script setup> block accesses variables destructured from defineProps. Default values can be declared using JavaScript's native default value syntax: const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()
withDefaults compiler macro for type-based props
In Vue 3.4 and below, when using type-based props declaration without Reactive Props Destructure, the withDefaults compiler macro is needed to declare default values: const props = withDefaults(defineProps<Props>(), { msg: 'hello', labels: () => ['one', 'two'] }). Default values for mutable reference types should be wrapped in functions to avoid accidental modification and external side effects. In 3.5+, default values can be declared directly with destructure syntax.
defineModel compiler macro
defineModel is a compiler macro available in 3.4+ used to declare a two-way binding prop that can be consumed via v-model from the parent component. If the first argument is a literal string, it will be used as the prop name; otherwise the prop name defaults to 'modelValue'. An additional object can include the prop's options and the model ref's value transform options. The macro emits an 'update:' event when the model is mutated.
defineModel examples
const model = defineModel() declares 'modelValue' prop, emits 'update:modelValue' when mutated. const model = defineModel({ type: String }) declares 'modelValue' prop with options. const count = defineModel('count') declares 'count' prop, emits 'update:count' when mutated. const count = defineModel('count', { type: Number, default: 0 }) declares 'count' prop with options.
defineModel de-synchronization pitfall
If defineModel has a default value and the parent does not provide a value for the prop, it can cause de-synchronization between parent and child components. For example, if a child has const model = defineModel({ default: 1 }) and the parent passes <Child v-model="myRef"></Child> where myRef is undefined, the parent's myRef is undefined but the child's model is 1.
defineModel modifiers destructuring
To access modifiers used with the v-model directive, destructure the return value of defineModel(): const [modelValue, modelModifiers] = defineModel(). This corresponds to v-model with modifiers like v-model.trim.
defineModel get and set transformers
Transform values when reading or syncing them back to parent using get and set transformer options in defineModel: const [modelValue, modelModifiers] = defineModel({ set(value) { if (modelModifiers.trim) { return value.trim() } return value } })
defineModel TypeScript usage
defineModel can receive type arguments: const modelValue = defineModel<string>() returns Ref<string | undefined>. const modelValue = defineModel<string>({ required: true }) returns Ref<string>. const [modelValue, modifiers] = defineModel<string, 'trim' | 'uppercase'>() receives Record<'trim' | 'uppercase', true | undefined>.
defineExpose compiler macro
Components using <script setup> are closed by default - the public instance retrieved via template refs or $parent chains will not expose any bindings declared inside <script setup>. Use the defineExpose compiler macro to explicitly expose properties: defineExpose({ a, b }). When a parent gets an instance via template refs, refs are automatically unwrapped just like on normal instances.
defineOptions compiler macro
defineOptions is a compiler macro available in 3.3+ used to declare component options directly inside <script setup> without a separate <script> block: defineOptions({ inheritAttrs: false, customOptions: { /* ... */ } }). The options will be hoisted to module scope and cannot access local variables in <script setup> that are not literal constants.
defineSlots TypeScript macro
defineSlots is a compiler macro available in 3.3+ used to provide type hints to IDEs for slot name and props type checking. It only accepts a type parameter and no runtime arguments. The type parameter should be a type literal where the property key is the slot name, and the value type is the slot function: defineSlots<{ default(props: { msg: string }): any }>(). It returns the slots object, equivalent to setupContext.slots or useSlots().
useSlots and useAttrs in script setup
useSlots and useAttrs helpers can be used inside <script setup> to access slots and attrs. They are actual runtime functions that return the equivalent of setupContext.slots and setupContext.attrs. They are imported from 'vue' and can be used in normal composition API functions as well. Access them directly as $slots and $attrs in templates instead.
Script setup with normal script block
script setup can be used alongside normal <script>. A normal <script> may be needed to declare options that cannot be expressed in <script setup>, declare named exports, or run side effects or create objects that should only execute once. Normal <script> is executed in module scope only once, while <script setup> is executed in setup() scope for each instance.
Script setup and normal script restrictions
Do NOT use a separate <script> section for options that can already be defined using <script setup>, such as props and emits. Variables created inside <script setup> are not added as properties to the component instance, making them inaccessible from the Options API. Mixing APIs in this way is strongly discouraged. If in an unsupported scenario, consider switching to an explicit setup() function instead.
Top-level await in script setup
Top-level await can be used inside <script setup>. The resulting code will be compiled as async setup(). The awaited expression will be automatically compiled in a format that preserves the current component instance context after the await. async setup() must be used in combination with Suspense, which is currently an experimental feature.
Generic type parameters in script setup
Generic type parameters can be declared using the generic attribute on the <script> tag: <script setup lang="ts" generic="T">. The value of generic works exactly the same as the parameter list between <...> in TypeScript. Multiple parameters, extends constraints, default types, and imported types can be used: generic="T extends string | number, U extends Item".
@vue-generic directive for explicit types
@vue-generic directive can be used to pass in explicit types when the type cannot be inferred: <!-- @vue-generic {import('@/api').Actor} --> <ApiSelect v-model="peopleIds" endpoint="/api/actors" id-prop="actorId" />
Generic component refs with vue-component-type-helpers
To use a reference to a generic component in a ref, use the vue-component-type-helpers library as InstanceType won't work. Import ComponentExposed from 'vue-component-type-helpers' and use: ref<ComponentExposed<typeof genericComponent>>() for generic components.
Script setup restrictions with src attribute
Due to the difference in module execution semantics, code inside <script setup> relies on the context of an SFC. When moved into external .js or .ts files, it may lead to confusion for both developers and tools. Therefore, <script setup> cannot be used with the src attribute.
Script setup does not support In-DOM Root Component Template
<script setup> does not support In-DOM Root Component Template.
Compiler macro limitations in script setup
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. They cannot be conditionally used, moved to separate functions, or used with runtime values. The options passed cannot reference local variables in setup scope, only imported bindings.
Type declaration limitations for generic components in 3.2
In Vue 3.2 and below, the generic type parameter for defineProps() was limited to a type literal or a reference to a local interface. This limitation was resolved in 3.3. The latest version of Vue supports referencing imported and a limited set of complex types in the type parameter position. Complex types that require actual type analysis, like conditional types, are not supported for the entire props object but can be used for the type of a single prop.
Script setup is recommended syntax
<script setup> is the recommended syntax when using both SFCs and Composition API. It provides more succinct code with less boilerplate, ability to declare props and emitted events using pure TypeScript, better runtime performance (template is compiled into a render function in the same scope without an intermediate proxy), and better IDE type-inference performance.
script setup syntax automatic template access
Top-level imports, variables and functions declared in <script setup> are automatically usable in the template of the same component. The template behaves as a JavaScript function declared in the same scope and naturally has access to everything declared alongside it.
Reactive props destructuring with defineProps
When defineProps is used with destructuring in <script setup>, a compile-time transform applies automatically. This allows destructuring props while retaining reactivity and supporting default values and local aliasing directly in the destructuring pattern.
$$() on destructured props converts to toRef
When using $$() on destructured props, the compiler converts it with toRef for efficiency. The macro works on destructured props since they are reactive variables.
defineProps destructuring with defaults example
Example showing reactive props destructuring with defineProps:
```html
<script setup lang="ts">
interface Props {
msg: string
count?: number
foo?: string
}
const {
msg,
count = 1,
foo: bar
} = defineProps<Props>()
watchEffect(() => {
console.log(msg, count, bar)
})
</script>
```
Default values and aliasing work directly in the destructuring pattern.