Composition API: setup()
Basic Usage
The setup()
hook serves as the entry point for Composition API usage in components in the following cases:
- Using Composition API without a build step;
- Integrating with Composition-API-based code in an Options API component.
Note
If you are using Composition API with Single-File Components, <script setup>
is strongly recommended for a more succinct and ergonomic syntax.
We can declare reactive state using Reactivity APIs and expose them to the template by returning an object from setup()
. The properties on the returned object will also be made available on the component instance (if other options are used):
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>
refs returned from setup
are automatically shallow unwrapped when accessed in the template so you do not need to use .value
when accessing them. They are also unwrapped in the same way when accessed on this
.
setup()
itself 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.
Accessing Props
The first argument in the setup
function is the props
argument. Just as you would expect in a standard component, props
inside of a setup
function are reactive and will be updated when new props are passed in.
js
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
Note that 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
.
If you really need to destructure the props, or need to pass a prop into an external function while retaining reactivity, you can do so with the toRefs() and toRef() utility APIs:
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')
}
}
Setup Context
The second argument passed to the setup
function is a Setup Context object. The context object exposes other values that may be useful inside setup
:
js
export default {
setup(props, context) {
// Attributes (Non-reactive object, equivalent to $attrs)
console.log(context.attrs)
// Slots (Non-reactive object, equivalent to $slots)
console.log(context.slots)
// Emit events (Function, equivalent to $emit)
console.log(context.emit)
// Expose public properties (Function)
console.log(context.expose)
}
}
The context object is not reactive and can be safely destructured:
js
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
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
. Also note that, 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.
Exposing Public Properties
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:
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 })
}
}
Usage with Render Functions
setup
can also return a render function which can directly make use of the reactive state declared in the same scope:
js
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
Returning a render function prevents us from returning anything else. Internally that shouldn't be a problem, but it can be problematic if we want to expose methods of this component to the parent component via template refs.
We can solve this problem by calling expose()
:
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 would then be available in the parent component via a template ref.