Exercises
Three small Vue 3 components to lock in the Composition API.
Three short challenges
EXAMPLE
<!-- 1. Counter with a computed and watcher -->
<script setup lang='ts'>
import { ref, computed, watch } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
watch(count, (n, prev) => {
console.log('went from', prev, 'to', n);
});
</script>
<template>
<button @click='count++'>count: {{ count }}</button>
<p>doubled: {{ doubled }}</p>
</template>
<!-- 2. v-model on a custom input -->
<!-- ChildInput.vue -->
<script setup lang='ts'>
const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<(e: 'update:modelValue', value: string) => void>();
</script>
<template>
<input
:value='props.modelValue'
@input='emit("update:modelValue", ($event.target as HTMLInputElement).value)'
/>
</template>
<!-- Parent.vue -->
<script setup lang='ts'>
import { ref } from 'vue';
import ChildInput from './ChildInput.vue';
const name = ref('');
</script>
<template>
<ChildInput v-model='name' />
<p>hello, {{ name || 'stranger' }}</p>
</template>
<!-- 3. Async list with Suspense + useFetch composable -->
<!-- useFetch.ts -->
import { ref } from 'vue';
export async function useFetch<T>(url: string) {
const data = ref<T | null>(null);
data.value = await fetch(url).then(r => r.json());
return { data };
}
<!-- UserList.vue -->
<script setup lang='ts'>
import { useFetch } from './useFetch';
const { data } = await useFetch<{ id: number; name: string }[]>('/api/users');
</script>
<template>
<ul>
<li v-for='u in data' :key='u.id'>{{ u.name }}</li>
</ul>
</template>
<!-- Parent uses Suspense -->
<template>
<Suspense>
<template #default><UserList /></template>
<template #fallback>Loading...</template>
</Suspense>
</template>
Why it matters
Composition API rewards extraction. Once you see useFetch / useCounter / useLocalStorage patterns in real apps, you stop reaching for Vuex/Pinia for things that should just be a composable.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…