#vue
#javascript
#frontend
Day 9: Reusability with Composables
Welcome to Day 9! If you were using Vue 2, you might remember “Mixins”. Composables essentially replace Mixins, but better (no name collisions, explicit dependencies).
What is a Composable?
It handles stateful logic. It’s just a function that uses ref, computed, or watch and returns them.
By convention, use the prefix use... (e.g., useMouse, useFetch).
Example: useMouse
composables/useMouse.ts
import { ref, onMounted, onUnmounted } from 'vue'
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(event: MouseEvent) {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// Return the refs so components can use them
return { x, y }
}
Using it in a Component
<script setup lang="ts">
import { useMouse } from './composables/useMouse'
// Hooks into the component's lifecycle automatically!
const { x, y } = useMouse()
</script>
<template>
<p>Mouse position: {{ x }}, {{ y }}</p>
</template>
Why is this awesome?
- Logic Extraction: Your component implementation becomes cleaner.
- Reusability: Use
useMousein 50 different components. - Organization: Group code by feature, not by option (
data,methods,mounted).
Async Composable: useFetch
Let’s build something more practical. A wrapper around fetch.
composables/useFetch.ts
import { ref } from 'vue'
export function useFetch(url: string) {
const data = ref(null)
const error = ref(null)
const loading = ref(true)
fetch(url)
.then(res => res.json())
.then(json => (data.value = json))
.catch(err => (error.value = err))
.finally(() => (loading.value = false))
return { data, error, loading }
}
Component
<script setup lang="ts">
import { useFetch } from './composables/useFetch'
const { data, loading, error } = useFetch('https://api.example.com/user')
</script>
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Oops! {{ error }}</div>
<div v-else>
<h1>{{ data.name }}</h1>
</div>
</template>
Challenge for Day 9
- Create a composable
useCounter(initialValue). - It should return
count,increment, anddecrement. - Use it in two different components.
- Observation: Does changing the count in Component A modify Component B? (Answer: No! Each call to
useCountercreates fresh state).
Solution:
// composables/useCounter.ts
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() { count.value++ }
function decrement() { count.value-- }
return { count, increment, decrement }
}
You have now mastered the Composition part of the Composition API. Tomorrow, we start building “Real” SPAs with Vue Router.