Day 2: Reactivity Core - ref() vs reactive()
Welcome to Day 2! Yesterday we built a static component. Today, we make it reactive.
In traditional JS, if you have let a = 1 and let b = a * 2, changing a later doesn’t update b. In Vue, we want our UI to update automatically when our data changes.
The Two Pillars: ref() and reactive()
Vue 3’s Composition API gives us two ways to declare reactive state.
1. ref()
Use this for primitives (strings, numbers, booleans) or when you want to replace an entire object.
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const message = ref("Hello")
function increment() {
// In script, you MUST use .value
count.value++
}
</script>
<template>
<!-- In template, .value is UNWRAPPED automatically -->
<button @click="increment">Count is: {{ count }}</button>
<p>{{ message }}</p>
</template>
Key Takeaway: ref wraps your value in an object { value: ... }.
2. reactive()
The reactive() function works only on objects (including arrays and Map/Set). It creates a deep proxy of the original object.
<script setup lang="ts">
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Jack'
}
})
function increment() {
// No .value needed!
state.count++
}
</script>
<template>
<button @click="increment">{{ state.count }}</button>
<p>User: {{ state.user.name }}</p>
</template>
Common Pitfall: You cannot destructure a reactive object comfortably without losing reactivity (unless you use toRefs).
Best Practice: Start with
ref()everywhere. It’s clearer when you are accessing reactive state because of.value. Switch toreactive()only when you have a large grouped state logic that naturally fits together.
Attribute Binding: v-bind
We know {{ }} outputs text. But what if we want to change an id, class, or disabled attribute dynamically? We use v-bind.
<script setup lang="ts">
import { ref } from 'vue'
const isRed = ref(true)
const imageSrc = ref("https://vuejs.org/images/logo.png")
</script>
<template>
<!-- Full syntax -->
<img v-bind:src="imageSrc" />
<!-- Shorthand (Use this!) -->
<img :src="imageSrc" />
<!-- Class binding is powerful -->
<div :class="{ red: isRed, bold: true }">
This might be red.
</div>
</template>
<style scoped>
.red { color: red; }
.bold { font-weight: bold; }
</style>
Challenge for Day 2
- Create a counter.
- Add a generic “Decrease” and “Increase” button.
- Display the count in the middle.
- Bonus: If the count is negative, make the text red.
Solution:
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<div class="counter">
<button @click="count--">-</button>
<!-- Dynamic class binding -->
<span :class="{ negative: count < 0 }">{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<style scoped>
.negative {
color: red;
font-weight: bold;
}
</style>
You are now controlling DOM updates purely by modifying state. No document.querySelector needed! Tomorrow, we learn to control what is rendered.