Published
- 7 min read
Episode 10: Setup and Composition API
Ponkichi’s Vue.js Adventure: The Day His Life Changed at the Forest Doctor’s Lab
Note
This story was translated and written with the help of generative AI, aiming to make learning frontend technology enjoyable. Every effort has been made to ensure technical and translation accuracy, but we cannot guarantee that everything is absolutely perfect. Please treat this series as a friendly learning aid, and enjoy it with an open mind!
Characters
- Dr. Frontend: The wise Frontend Doctor who lives in a laboratory deep in the forest. He knows everything about frontend technology and always answers Ponkichi’s innocent questions with kindness and humor.
- Ponkichi: A curious young raccoon dog who dreams of becoming a frontend engineer. He has just started learning Vue.js and is fascinated by its depth. When he discovers something new, he can’t help but jump up with a cheerful “Boing!”
Episode 10🦝: Setup and Composition API
Having learned about the type safety of Props
, Ponkichi now dives into the mystery of the setup
function, which the doctor called “the heart of Vue 3.”
Ponkichi:
“Doctor! Please teach me about the setup
function! Last time, it was taking props
as an argument, but what exactly was it doing?”
Dr. Frontend:
“Indeed. The setup
function is the central place for the new way of writing components called ‘Composition API,’ introduced in Vue 3. It would be good to first understand why Composition API was created.”
The doctor displayed a component written with Options API.
// Options API (traditional way)
export default defineComponent({
data() {
return {
count: 0
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
})
Dr. Frontend:
“In the traditional Options API, data
goes here, methods
go there, and computed
goes over there—related code was scattered across different sections based on the type of functionality. This works fine for small components, but as they grow larger, logic related to a single feature gets scattered throughout the file, making it hard to follow.”
Ponkichi: “That’s true… I can imagine having to search all over the place wondering ‘Where is the logic for this button written?’”
Dr. Frontend:
“Exactly right. That’s where Composition API comes in. It’s a new style that allows you to write related code for each feature in one place. And the stage for this is the setup
function.”
The doctor showed an example of a counter component using the setup
function.
import { defineComponent, ref, computed } from 'vue'
export default defineComponent({
setup() {
// 1. Define reactive state
const count = ref(0) // Wrapping with ref() makes it reactive
// 2. Define methods that update state
function increment() {
count.value++
}
// 3. Define values derived from state
const doubleCount = computed(() => count.value * 2)
// 4. Return what should be exposed to the template
return {
count,
increment,
doubleCount
}
}
})
Ponkichi:
“Wow! There’s no data
or methods
! Instead, everything related to the counter is grouped together inside the setup
function! And there are new functions like ref
and computed
.”
Dr. Frontend:
“That’s right. ref
is a function for creating reactive data. And the properties of the object returned from the setup
function become available in the component’s template.”
Ponkichi:
“I see! Unlike Options API, where you have to write things in different places, all the related code is together in setup
, making it super clear! Boing!”
Dr. Frontend:
“Indeed. That’s the major advantage of Composition API. And this is where the second of the seven parameters, RawBindings
, comes into play.”
Ponkichi:
“RawBindings
…! What does that mean?”
Dr. Frontend:
“RawBindings
is the parameter that defines the ‘type’ of the object that this setup
function returns. In this example, a type like { count: Ref<number>, increment: () => void, doubleCount: ComputedRef<number> }
would be inferred as RawBindings
.”
Dr. Frontend:
“Because of this type information, TypeScript can accurately understand what’s returned from setup
, enabling more powerful type checking within templates and stronger type inference for this
.”
Ponkichi understood that the setup
function wasn’t just a new way of writing code, but a powerful mechanism for creating more robust and understandable code in collaboration with TypeScript.
Ponkichi:
“setup
and Composition API, and RawBindings
. Everything connects together to create safe and organized components!”
Dr. Frontend:
“Exactly right! However, Ponkichi, don’t you think writing defineComponent
and setup
every time, and having to return
at the end, is a bit tedious?”
Ponkichi: “Eh…? Ah, yes, a little bit…”
Dr. Frontend:
“Hehe. Actually, Vue has even more magic that lets you write much more simply. Next time, let’s look at the magic spell <script setup>
and the mysterious power of type inference.”
Ponkichi: “What?! It can get even simpler?! I can’t wait!”
🌟 Today’s Takeaways
- Composition API is a new way of writing that groups related code by feature.
- The
setup
function is the central place for writing Composition API logic. ref
can be used to create reactive data.- Objects returned from
setup
become available in the template. - One of the seven parameters,
RawBindings
, defines the type of the object returned bysetup
.
Next Episode Preview: “The Mysterious Power of Type Inference”
The magic spell <script setup>
appears, eliminating the need for defineComponent
, setup
, and return
! Why does it all work so seamlessly with just that? We’ll explore the powerful force of TypeScript’s “type inference”!
👨🏫 Doctor’s Note
Ponkichi might have been a bit puzzled by the count.value++
syntax this time. Many readers probably thought “Why can’t we just write count++
?”
This will be a somewhat technical discussion, but the answer to this question involves deep technical background of JavaScript and Vue. It’s perfectly fine if you can’t understand everything completely—just thinking “Oh, so there’s such a reason” is enough.
Actually, this .value
is a beautiful “compromise product” born from the deep constraints of the JavaScript language and the ingenious solution Vue devised.
First, to achieve reactivity (a mechanism that automatically detects value changes), we need to “catch the moment when a value changes.” Vue 2 used Object.defineProperty
:
// Vue 2's reactivity (simplified)
let data = {}
Object.defineProperty(data, 'count', {
get() {
console.log('Value was read!')
return this._count
},
set(newValue) {
console.log('Value was changed!', newValue)
this._count = newValue
// DOM updates are queued and batched in the next tick
}
})
But this had major problems. You couldn’t add new properties later, and you couldn’t detect array index operations.
Vue 3 solved this problem with Proxy:
// Vue 3's reactivity (simplified)
const reactive = (obj) => {
return new Proxy(obj, {
get(target, key) {
console.log(`${key} was read!`)
return target[key]
},
set(target, key, value) {
console.log(`${key} was changed to ${value}!`)
target[key] = value
// Update DOM
return true
}
})
}
This made reactivity for objects perfect. But here, a new problem arose.
Primitive values (numbers, strings, booleans) are not objects!
let count = 5 // This is a number, can't wrap it with Proxy
count = 6 // How do we detect this change?
So Vue’s solution was to wrap primitive values in objects:
// The essence of ref() (super simplified)
function ref(value) {
return reactive({
value: value // Store primitive value in value property
})
}
const count = ref(5)
// count is an object like { value: 5 }
// By doing count.value = 6, Proxy can detect the change!
In other words, the count.value++
syntax is a “beautiful hack that Vue devised to overcome JavaScript’s constraints.”
Interestingly, you don’t need to write .value
in templates:
<template>
<!-- No need to write .value! -->
<div>{{ count }}</div>
</template>
This is because Vue’s compiler automatically adds .value
for you. It’s a very thoughtful design that considers developer experience.
Behind this seemingly tedious .value
syntax lies the battle with JavaScript language limitations, the pursuit of performance, and consideration for developer experience—all packed together.
End of Episode 10