SyntaxStudy
Sign Up
Vue.js Advanced Composable Patterns
Vue.js Beginner 1 min read

Advanced Composable Patterns

Composables become especially powerful when they accept reactive arguments. Using `toValue()` (Vue 3.3+) or `isRef` / `unref` inside a composable lets it accept both plain values and refs interchangeably. `watchEffect` automatically re-runs the composable logic when a reactive argument changes, making the composable itself a reactive transformation pipeline. The `useEventListener` pattern is a clean example: it registers a DOM event listener in `onMounted` and removes it in `onUnmounted`, taking element, event, and handler as arguments. This avoids repeating the mount/unmount pattern across components. Similarly, `useLocalStorage` wraps a `ref` so that reads and writes are automatically synchronised with `localStorage`. For composables that need to expose an action alongside state — `useToggle`, `useCounter`, `useBoolean` — returning a tuple `[state, action]` mirrors the React hooks convention and makes the usage ergonomic. Always document the return shape of your composables; TypeScript return types make composable APIs self-documenting and refactoring-safe.
Example
// src/composables/useLocalStorage.ts
import { ref, watch } from 'vue';

export function useLocalStorage<T>(key: string, defaultValue: T) {
  const stored = localStorage.getItem(key);
  const data   = ref<T>(stored ? JSON.parse(stored) : defaultValue);

  watch(data, (val) => {
    localStorage.setItem(key, JSON.stringify(val));
  }, { deep: true });

  return data;
}

// src/composables/useToggle.ts
import { ref } from 'vue';
export function useToggle(initial = false) {
  const state  = ref(initial);
  const toggle = () => { state.value = !state.value; };
  return [state, toggle] as const;
}

// src/composables/useEventListener.ts
import { onMounted, onUnmounted } from 'vue';
export function useEventListener(
  target: EventTarget,
  event: string,
  handler: EventListener
) {
  onMounted(()   => target.addEventListener(event, handler));
  onUnmounted(() => target.removeEventListener(event, handler));
}

// ── Usage in a component ──────────────────────
<script setup>
import { useLocalStorage } from '@/composables/useLocalStorage';
import { useToggle }       from '@/composables/useToggle';

const theme         = useLocalStorage('theme', 'light');
const [isDark, toggleDark] = useToggle();
</script>