In Vue 3, what arguments does a component's setup() function receive, and what is it allowed to return?
answer
- two arguments, one of them reactive
- attrs, slots, emit, expose
- no this inside
- object of bindings or a function
- runs once, before any options hook
basics
~20 sVue 3's setup(props, context) receives reactive, read-only props and a context with attrs, slots, emit and expose; it runs once per instance with no this, and returns an object of template bindings or a render function.
solid answer
~40 s`setup()` is the Composition API entry point for a component written without `<script setup>`. Its first argument, `props`, is a reactive object of the resolved props; read it as `props.title` to stay reactive, and treat it as read-only. The second, the **setup context**, holds `attrs`, `slots` and `emit` (the counterparts of `$attrs`, `$slots` and `$emit`) plus `expose`, which limits what a parent's template ref can reach. Vue calls `setup()` as a plain function, so `this` is `undefined`; everything arrives through the two arguments. It runs once per instance, before any Options API hook. It returns either an object, whose properties the template can use with refs shallow-unwrapped, or a render function, and it should return synchronously unless the component sits under `<Suspense>`.
code
ts · 20 linesimport { defineComponent, ref, computed } from 'vue'
export default defineComponent({
props: { start: { type: Number, default: 0 } },
emits: ['change'],
setup(props, { attrs, slots, emit, expose }) {
const count = ref(props.start) // initial snapshot of the prop, on purpose
const label = computed(() => `Count: ${count.value}`)
function increment() {
count.value++
emit('change', count.value) // not this.$emit: this is undefined here
}
expose({ increment }) // what a parent's template ref may call
return { count, label, increment } // bindings for the template; refs are unwrapped
},
template: `<button @click="increment">{{ label }}</button>`,
})go deeper
Recall the signature, setup(props, context), the four context members, that this is undefined, and the two return shapes.
Explain props reactivity inside setup, why the context can be destructured but attrs and slots should be read at use time, and ref unwrapping on return.
Spot setup mistakes in review: this.$emit, returned VNodes, prop snapshots copied into locals, and async setup without a Suspense ancestor.
Decide where explicit setup() still belongs in a codebase that defaults to script setup, such as no-build pages or render-function libraries.
## Where setup() fits In Vue 3, a component can declare a `setup` option: a function Vue calls once when it creates each instance of the component. It is the entry point for the Composition API in two situations: when you use Vue without a build step, and when you integrate Composition API code into a component that otherwise uses options. In Single-File Components, `<script setup>` is the recommended, shorter form, and the SFC compiler turns it into a `setup()` function. ## The first argument: props `props` is a **reactive** object containing the resolved props, after defaults and type casting. Reading `props.title` inside a `computed()` or a watcher tracks it, so those effects update when the parent passes a new value. Two cautions: - **Read through the object.** Copying `props.title` into a local variable takes a one-time snapshot. The body of `setup()` is not itself a reactive effect; Vue pauses dependency tracking while it runs. - **Treat it as read-only.** Props flow down from the parent. Changing a value is done by emitting an event, not by assigning to `props`. ## The second argument: the setup context | Member | What it is | Options API counterpart | |---|---|---| | `attrs` | fallthrough attributes not declared as props or emits | `$attrs` | | `slots` | slot functions passed by the parent | `$slots` | | `emit` | function that emits a declared event | `$emit` | | `expose` | function that limits what a parent's template ref sees | none; the `expose` option is closest | The context object itself is plain and not reactive, so destructuring it in the parameter list, `setup(props, { attrs, slots, emit, expose })`, is safe. `attrs` and `slots` are different: Vue updates them in place whenever the component updates, so always read `attrs.x` or `slots.default` at the point of use instead of destructuring their properties into local variables. ## Why there is no this Vue calls `setup()` as a plain function rather than as a method on the instance, so `this` is `undefined` inside it. That is deliberate. State lives in local variables created with `ref()` and `reactive()`, and the component's capabilities arrive through the two parameters. It also runs before options such as `data`, `computed` and `methods` are applied, so there would be nothing useful to read on `this` anyway. The relationship goes one way: Options API code can read what `setup()` returns through `this`, but `setup()` cannot read options. ## What setup() may return 1. **An object of bindings.** Its properties are available in the template, and in other options through `this`. Refs in it are **shallow-unwrapped**, so the template writes `{{ count }}`, not `{{ count.value }}`. 2. **A render function.** Vue uses it as the component's render function. It closes over the local state directly, which is the natural way to write a component in a `.ts` or `.tsx` file without a template. 3. **Nothing,** for a component whose template needs no bindings. Returning something else produces development warnings: a VNode triggers `setup() should not return VNodes directly - return a render function instead.`, and a non-object value triggers `setup() should return an object.` A VNode is a one-time snapshot; a function lets Vue re-run it inside the component's render effect whenever tracked state changes. ## Timing and synchronous return - `setup()` runs **once per component instance**, when the instance is created, before the first render and before any Options API hook, `beforeCreate` included. - It should return **synchronously**. An `async setup()` returns a promise, which Vue can only wait for when the component has a `<Suspense>` ancestor. - Lifecycle hooks such as `onMounted()` and watchers created in its synchronous body are bound to the instance and cleaned up on unmount. ## Common mistakes - Using `this.$emit` inside `setup()`: `this` is `undefined`; use the `emit` from the context. - Returning `h('div')` instead of `() => h('div')`, which renders once and never updates. - Expecting `setup()` to run again when props change; only effects created inside it re-run.
- What happens if setup() returns a VNode such as h('div') instead of a render function?Vue warns in development that `setup()` should not return VNodes directly and should return a render function instead. A VNode is a single snapshot, so the output would never update. Returning `() => h('div', ...)` lets Vue call it inside the component's render effect, which re-runs whenever the reactive state it reads changes.
- When does setup() run relative to the component's lifecycle hooks?Once, while the instance is being created: before the first render and before every Options API hook, including `beforeCreate`. Hooks registered inside it, such as `onMounted()`, fire later at their own stage. A props change re-runs the effects setup created, never setup itself.
saying these in an interview costs you the question
- Inside setup(), this points to the component instance just as in methods.
- setup() runs again whenever the component's props change.
- The context argument is reactive, so destructuring it breaks updates.
- Returning h('div') from setup() gives the component a render function.
- The template must use count.value for refs returned from setup().