The setup() Function and <script setup>
The setup() function is the entry point for the Composition API in Vue 3. It runs once, before the component is created, before data, computed properties, and methods exist in the Options API sense, and before the component instance itself is fully assembled. Inside setup(), you create reactive state, computed values, and functions, then return them so the template can access them. <script setup> is a compile-time syntax sugar that removes almost all of the boilerplate setup() requires — no explicit return statement, no wrapping function, and direct access to imports and top-level bindings from the template.
Cricket analogy: setup() is like the pre-match team meeting that happens once before the toss — before the scoreboard, stats, or lineup officially exist, the coach assembles the game plan and hands (returns) it to the players; <script setup> is like a modern huddle where the plan is simply understood by everyone present, no formal handout needed.
Using setup() directly
Before <script setup> existed, Composition API code lived inside an explicit setup() function defined in the component options object. Anything the template needs must be explicitly returned from setup(), which is easy to forget and adds visual noise, especially as a component grows.
Cricket analogy: Before modern digital scoreboards, a scorer had to explicitly write every stat onto a physical board (explicit return) or it simply wouldn't be visible to the crowd — forgetting to chalk up a wicket was an easy, visually noisy mistake as the scoresheet grew longer through a long innings.
<script>
import { ref, computed } from 'vue'
export default {
props: ['initialCount'],
setup(props) {
const count = ref(props.initialCount)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubled, increment }
}
}
</script>The same component with <script setup>
<script setup> compiles to the equivalent of a setup() function under the hood, but every top-level binding — variables, functions, imports — is automatically exposed to the template without an explicit return. Props and emits are declared with the defineProps and defineEmits compiler macros, which need no import because the compiler recognizes and transforms them at build time.
Cricket analogy: Under the hood, a modern digital scoreboard still runs the same scoring logic as the old manual one, but every stat a scorer enters is automatically shown to the crowd without a manual "publish" step, and standard signals like "wide" or "no-ball" (defineProps/defineEmits) are built-in umpire calls needing no extra rulebook lookup.
<script setup>
import { ref, computed } from 'vue'
const props = defineProps<{ initialCount: number }>()
const count = ref(props.initialCount)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">{{ count }} (doubled: {{ doubled }})</button>
</template>What setup() receives
When used explicitly, setup(props, context) receives two arguments: a reactive props object (which should not be destructured directly, since that loses reactivity — destructure with toRefs if needed) and a context object exposing attrs, slots, and emit. <script setup> provides equivalent access through defineProps(), defineEmits(), useSlots(), and useAttrs(), so you rarely need the raw setup(props, context) signature anymore.
Cricket analogy: The explicit setup(props, context) form is like a scorer receiving a full stat sheet (props) that loses live-updating if copied field by field (must use toRefs), plus a toolkit (context) for extra signals and umpire calls (attrs/slots/emit); the modern approach uses simpler dedicated tools instead of the raw two-argument handoff.
<script setup> is now the idiomatic, recommended way to author Vue 3 components — the Vue team and most style guides treat explicit setup() as a lower-level, less ergonomic form, useful mainly for advanced patterns (like manually returning a render function) or codebases that predate the <script setup> syntax.
A common mistake when using the explicit setup(props) form is destructuring props directly, e.g. const { initialCount } = props, which breaks reactivity because it copies the primitive value out of Vue's reactive proxy at that instant. Access props.initialCount directly, or use toRefs(props) if you truly need standalone refs.
- setup() is the Composition API's entry point, running before the component instance is fully created.
- Explicit setup() must return an object of everything the template needs to access.
- <script setup> is compiler sugar: top-level bindings are auto-exposed to the template, no return statement needed.
- defineProps and defineEmits are compiler macros available without import inside <script setup>.
- setup(props, context) exposes attrs, slots, and emit via its context argument; <script setup> offers useAttrs()/useSlots() equivalents.
- Never destructure props directly, as it strips reactivity — access via props.x or wrap with toRefs().
Practice what you learned
1. What must an explicit setup() function do to expose values to the template?
2. What does <script setup> do differently from explicit setup()?
3. How are props typically declared inside <script setup>?
4. Why is directly destructuring the props object considered a pitfall?
5. What does the context argument of setup(props, context) provide?
Was this page helpful?
You May Also Like
The Composition API vs Options API
Compares Vue's two component authoring styles, explaining why the Composition API was introduced and when each approach makes sense in real projects.
Lifecycle Hooks in the Composition API
Learn how onMounted, onUpdated, onUnmounted and other lifecycle hook functions let you run code at specific points in a component's life.
Props Explained
Explains how props pass data from parent to child components in Vue, covering declaration, validation, defaults, and the one-way data flow rule.
Emitting Custom Events
Learn how child components communicate upward to parents using Vue's custom event system, including typed emits and the defineEmits macro.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics