Skip to main content

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:

html
<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:

  1. We import ref from Vue to create a reactive variable
  2. We define a count ref initialized with 0
  3. We define an increment method that increases the count
  4. We return both the count and increment to make them available in the template

Setup Function Parameters

The setup function accepts two parameters:

  1. props - The resolved props of the component
  2. context - An object containing specific properties (attrs, slots, emit, expose)

Working with Props

html
<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

html
<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 component
  • slots: Component slots
  • emit: Method to emit events
  • expose: 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:

html
<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:

html
<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:

  1. Creating reactive state with ref
  2. Using computed properties with computed
  3. Using lifecycle hooks with onMounted
  4. Organizing related functionality together
  5. 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:

html
<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>

html
<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>

html
<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

  1. Keep the setup function focused: Don't overcomplicate it with too much logic. Extract complex functionality into separate composable functions.

  2. Use meaningful variable and function names: This makes your code more readable and self-documenting.

  3. 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.

  4. Return only what's needed in the template: Only expose the variables and functions that your template actually uses.

  5. Use TypeScript for better type safety: The setup function works well with TypeScript, giving you better autocomplete and error checking.

Common Mistakes to Avoid

  1. Forgetting to use .value when working with refs:
javascript
// Wrong
const count = ref(0)
count++ // This won't work!

// Correct
count.value++
  1. Destructuring props without toRefs:
javascript
// Wrong - loses reactivity
const { title } = props

// Correct - maintains reactivity
const { title } = toRefs(props)
  1. Not returning values from setup:
javascript
// 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 and context 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

Exercises

  1. Convert an existing component from Options API to Composition API using the setup function.
  2. Create a simple counter component with increment and decrement functions using the setup function.
  3. Build a form with validation using the Composition API.
  4. Create a composable function for fetching data and use it in a component's setup function.
  5. 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! :)