Vue.js Setup Function
Introduction
The setup function is the heart of Vue.js Composition API, introduced in Vue 3. It serves as the entry point for using the Composition API within components and provides a more flexible way to organize component logic compared to the Options API used in earlier Vue versions.
In this guide, you'll learn:
- What the
setupfunction is and why it's useful - How to implement the
setupfunction in your components - Best practices for working with the
setupfunction - Real-world examples of the
setupfunction in action
What is the Setup Function?
The setup function is a special hook in Vue.js Composition API that runs before the component instance is created, before props are resolved, and before the component's lifecycle hooks are called. It provides a place where you can define reactive data, computed properties, methods, and lifecycle hooks all in one place.
Key Features:
- Runs before component creation
- Provides access to props and context
- Returns an object with properties and methods that will be accessible in the template
- Can expose lifecycle hooks and watchers
- Enables better organization of component logic by feature instead of by option type
Basic Syntax
Here's a basic example of a component using the setup function:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// Declare a reactive state
const count = ref(0)
// Define a method
const increment = () => {
count.value++
}
// Return everything that should be available in the template
return {
count,
increment
}
}
}
</script>
In this example:
- We import
reffrom Vue to create a reactive variable - We define a
countref initialized with0 - We define an
incrementmethod that increases the count - We return both the
countandincrementto make them available in the template
Setup Function Parameters
The setup function accepts two parameters:
props- The resolved props of the componentcontext- An object containing specific properties (attrs,slots,emit,expose)
Working with Props
<script>
import { ref, toRefs } from 'vue'
export default {
props: {
initialCount: {
type: Number,
default: 0
}
},
setup(props) {
// Access props directly
console.log(props.initialCount)
// Destructure with toRefs for reactivity
const { initialCount } = toRefs(props)
const count = ref(initialCount.value)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
}
</script>
When working with props in the setup function:
- Props are reactive, but you can't destructure them directly (you'll lose reactivity)
- Use
toRefsto destructure while maintaining reactivity - Props are available as soon as the
setupfunction runs
The Context Object
<script>
export default {
setup(props, context) {
// Using emit
const handleClick = () => {
context.emit('custom-event', 'Hello from child!')
}
// Using attrs
console.log(context.attrs)
// Using slots
console.log(context.slots)
// Using expose (to expose public methods and properties)
const publicMethod = () => {
// Do something...
}
context.expose({
publicMethod
})
return {
handleClick
}
}
}
</script>
The context object provides access to:
attrs: Non-prop attributes passed to the componentslots: Component slotsemit: Method to emit eventsexpose: Method to expose public properties to parent components
Using Lifecycle Hooks in Setup
In the Composition API, lifecycle hooks are imported and called directly within the setup function:
<script>
import { ref, onMounted, onUpdated, onBeforeUnmount } from 'vue'
export default {
setup() {
const count = ref(0)
onMounted(() => {
console.log('Component has been mounted!')
})
onUpdated(() => {
console.log('Component has been updated!')
})
onBeforeUnmount(() => {
console.log('Component will be unmounted!')
})
return {
count
}
}
}
</script>
Available lifecycle hooks:
onBeforeMountonMountedonBeforeUpdateonUpdatedonBeforeUnmountonUnmountedonActivatedonDeactivatedonErrorCaptured
Real-World Example: Todo List Application
Let's build a simple todo list application using the setup function:
<template>
<div class="todo-app">
<h1>{{ title }}</h1>
<form @submit.prevent="addTodo">
<input
v-model="newTodo"
placeholder="What needs to be done?"
/>
<button type="submit">Add</button>
</form>
<ul class="todo-list">
<li v-for="(todo, index) in todos" :key="index">
<input
type="checkbox"
v-model="todo.completed"
/>
<span :class="{ completed: todo.completed }">
{{ todo.text }}
</span>
<button @click="removeTodo(index)">Delete</button>
</li>
</ul>
<div class="stats">
<span>{{ activeCount }} items left</span>
<button @click="clearCompleted">Clear completed</button>
</div>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
export default {
setup() {
// Reactive state
const title = ref('My Todo List')
const newTodo = ref('')
const todos = ref([])
// Load todos from localStorage
onMounted(() => {
const savedTodos = localStorage.getItem('todos')
if (savedTodos) {
todos.value = JSON.parse(savedTodos)
}
})
// Watch for changes and save to localStorage
const saveTodos = () => {
localStorage.setItem('todos', JSON.stringify(todos.value))
}
// Computed property
const activeCount = computed(() => {
return todos.value.filter(todo => !todo.completed).length
})
// Methods
const addTodo = () => {
if (newTodo.value.trim()) {
todos.value.push({
text: newTodo.value,
completed: false
})
newTodo.value = ''
saveTodos()
}
}
const removeTodo = (index) => {
todos.value.splice(index, 1)
saveTodos()
}
const clearCompleted = () => {
todos.value = todos.value.filter(todo => !todo.completed)
saveTodos()
}
return {
title,
newTodo,
todos,
activeCount,
addTodo,
removeTodo,
clearCompleted
}
}
}
</script>
<style scoped>
.completed {
text-decoration: line-through;
color: #999;
}
</style>
This example demonstrates:
- Creating reactive state with
ref - Using computed properties with
computed - Using lifecycle hooks with
onMounted - Organizing related functionality together
- Returning values and methods for template use
Setup Function with <script setup>
Vue 3.2 introduced a more concise syntax called <script setup>, which simplifies the use of the Composition API even further:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// No setup function needed - everything is automatically in "setup scope"
const count = ref(0)
const increment = () => {
count.value++
}
</script>
With <script setup>:
- No need to manually write the
setup()function - No need to return values - all top-level bindings are automatically exposed to the template
- Better TypeScript integration
- More concise code
Accessing Props with <script setup>
<script setup>
import { ref } from 'vue'
// Define props with defineProps
const props = defineProps({
initialCount: {
type: Number,
default: 0
}
})
// Use props directly
const count = ref(props.initialCount)
const increment = () => {
count.value++
}
</script>
Emitting Events with <script setup>
<script setup>
// Define emits with defineEmits
const emit = defineEmits(['update', 'delete'])
const updateItem = () => {
emit('update', { id: 1, name: 'Updated Item' })
}
const deleteItem = () => {
emit('delete', 1)
}
</script>
Best Practices
-
Keep the
setupfunction focused: Don't overcomplicate it with too much logic. Extract complex functionality into separate composable functions. -
Use meaningful variable and function names: This makes your code more readable and self-documenting.
-
Group related logic together: One of the main advantages of the Composition API is the ability to organize code by logical concern rather than by option type.
-
Return only what's needed in the template: Only expose the variables and functions that your template actually uses.
-
Use TypeScript for better type safety: The
setupfunction works well with TypeScript, giving you better autocomplete and error checking.
Common Mistakes to Avoid
- Forgetting to use
.valuewhen working with refs:
// Wrong
const count = ref(0)
count++ // This won't work!
// Correct
count.value++
- Destructuring props without toRefs:
// Wrong - loses reactivity
const { title } = props
// Correct - maintains reactivity
const { title } = toRefs(props)
- Not returning values from setup:
// Wrong - count won't be available in template
setup() {
const count = ref(0)
// Missing return statement!
}
// Correct
setup() {
const count = ref(0)
return { count }
}
Summary
The setup function is the foundation of Vue.js Composition API, offering a more flexible and organized way to define component logic. Key takeaways include:
- The
setupfunction runs before the component is created - It accepts
propsandcontextas parameters - It returns an object with properties and methods for the template
- It allows organizing code by feature rather than by option type
- The newer
<script setup>syntax provides a more concise way to use the Composition API
By leveraging the power of the setup function, you can create more maintainable and scalable Vue.js applications with cleaner, more logical code organization.
Additional Resources
- Vue.js Official Documentation on Composition API
- Vue.js Official Documentation on Setup Function
- Vue.js Script Setup Documentation
Exercises
- Convert an existing component from Options API to Composition API using the
setupfunction. - Create a simple counter component with increment and decrement functions using the
setupfunction. - Build a form with validation using the Composition API.
- Create a composable function for fetching data and use it in a component's
setupfunction. - Refactor the todo list example to use
<script setup>syntax.
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)