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
setup
function is and why it's useful - How to implement the
setup
function in your components - Best practices for working with the
setup
function - Real-world examples of the
setup
function 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
ref
from Vue to create a reactive variable - We define a
count
ref initialized with0
- We define an
increment
method that increases the count - We return both the
count
andincrement
to 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
toRefs
to destructure while maintaining reactivity - Props are available as soon as the
setup
function 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:
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
onActivated
onDeactivated
onErrorCaptured
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
setup
function 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
setup
function works well with TypeScript, giving you better autocomplete and error checking.
Common Mistakes to Avoid
- Forgetting to use
.value
when 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
setup
function runs before the component is created - It accepts
props
andcontext
as 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
setup
function. - Create a simple counter component with increment and decrement functions using the
setup
function. - Build a form with validation using the Composition API.
- Create a composable function for fetching data and use it in a component's
setup
function. - Refactor the todo list example to use
<script setup>
syntax.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)