Unlocking Vue 3 Reactive Patterns with Composition API
Hoàng Ngô Anh Đức·
28/05/2026 02:12
Vue 3's **Composition API** and **Reactivity Engine** (rewritten completely using ES6 Proxies) have transformed frontend development. They offer incredible flexibility compared to the old Options API.
However, with great power comes great confusion. Beginners and intermediate developers alike often struggle with performance traps, reactive destructuring errors, and memory leaks. In this guide, we'll unlock the deep reactive patterns you need for premium, production-grade applications.
### 1. Ref vs. Reactive: The Ultimate Showdown
* **`ref()`:** Wrap any primitive values (String, Number, Boolean) or Objects. It creates a wrapper object containing a single reactive `.value` property.
* **`reactive()`:** Works **only** for objects, maps, arrays, or sets. It returns a reactive Proxy of the target object directly, without a `.value` wrapper.
#### The Destructuring Trap
One of the most common reactive bugs in Vue 3 is destructuring a `reactive` object:
```typescript
const state = reactive({ count: 0, user: 'Duc' });
// ❌ WARNING: This breaks reactivity! 'count' becomes a plain number.
const { count, user } = state;
```
To solve this, always use `toRefs()` to convert the reactive object's properties into individual refs:
```typescript
// ✓ Clean, responsive, and type-safe!
const { count, user } = toRefs(state);
```
### 2. Creating Premium Reusable Composables
A Composable is a function that leverages Vue's Composition API to encapsulate stateful logic. Let's build a production-ready `useFetch` composable that handles loading state, errors, and automatic cancellation:
```typescript
import { ref, watchEffect, type Ref } from 'vue';
export function useFetch(urlRef: Ref) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
watchEffect(async (onCleanup) => {
loading.value = true;
data.value = null;
error.value = null;
const controller = new AbortController();
onCleanup(() => controller.abort()); // Cancel ongoing requests automatically!
try {
const response = await fetch(urlRef.value, { signal: controller.signal });
if (!response.ok) throw new Error("HTTP error " + response.status);
data.value = await response.json();
} catch (e: any) {
if (e.name !== 'AbortError') {
error.value = e;
}
} finally {
loading.value = false;
}
});
return { data, error, loading };
}
```
This elegant composable cleans up after itself by automatically aborting the prior request if `urlRef` changes before the server responds!
// Read next
Related articles
Tailwind CSS v4 & Next-Gen Frontend Styling
Discover the future of utility-first CSS. Explore Tailwind v4's CSS-first engine, major performance boosts, and logical...
// Reader response
Comments
This article has no comments yet.
// Author
Hoàng Ngô Anh Đức
Software Engineering Student & Junior Full-Stack Developer
Tôi đang học Software Engineering tại RMIT University Vietnam, có 4 năm học tập tại Canada và kinh nghiệm làm việc remote với các tổ chức Canada. Tôi tập trung xây dựng sản phẩm web thực tế bằng Go, Vue, TypeScript và SQLite.