$npx -y skills add vuejs-ai/skills --skill create-adaptable-composableCreate a library-grade Vue composable that accepts maybe-reactive inputs (MaybeRef / MaybeRefOrGetter) so callers can pass a plain value, ref, or getter. Normalize inputs with toValue()/toRef() inside reactive effects (watch/watchEffect) to keep behavior predictable and reactive.
| 1 | # Create Adaptable Composable |
| 2 | |
| 3 | Adaptable composables are reusable functions that can accept both reactive and non-reactive inputs. This allows developers to use the composable in a variety of contexts without worrying about the reactivity of the inputs. |
| 4 | |
| 5 | Steps to design an adaptable composable in Vue.js: |
| 6 | 1. Confirm the composable's purpose and API design and expected inputs/outputs. |
| 7 | 2. Identify inputs params that should be reactive (MaybeRef / MaybeRefOrGetter). |
| 8 | 3. Use `toValue()` or `toRef()` to normalize inputs inside reactive effects. |
| 9 | 4. Implement the core logic of the composable using Vue's reactivity APIs. |
| 10 | |
| 11 | ## Core Type Concepts |
| 12 | |
| 13 | ### Type Utilities |
| 14 | |
| 15 | ```ts |
| 16 | /** |
| 17 | * value or writable ref (value/ref/shallowRef/writable computed) |
| 18 | */ |
| 19 | export type MaybeRef<T = any> = T | Ref<T> | ShallowRef<T> | WritableComputedRef<T>; |
| 20 | |
| 21 | /** |
| 22 | * MaybeRef<T> + ComputedRef<T> + () => T |
| 23 | */ |
| 24 | export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T); |
| 25 | ``` |
| 26 | |
| 27 | ### Policy and Rules |
| 28 | |
| 29 | - Read-only, computed-friendly input: use `MaybeRefOrGetter` |
| 30 | - Needs to be writable / two-way input: use `MaybeRef` |
| 31 | - Parameter might be a function value (callback/predicate/comparator): do not use `MaybeRefOrGetter`, or you may accidentally invoke it as a getter. |
| 32 | - DOM/Element targets: if you want computed/derived targets, use `MaybeRefOrGetter`. |
| 33 | |
| 34 | When `MaybeRefOrGetter` or `MaybeRef` is used: |
| 35 | - resolve reactive value using `toRef()` (e.g. watcher source) |
| 36 | - resolve non-reactive value using `toValue()` |
| 37 | |
| 38 | ### Examples |
| 39 | |
| 40 | Adaptable `useDocumentTitle` Composable: read-only title parameter |
| 41 | |
| 42 | ```ts |
| 43 | import { watch, toRef } from 'vue' |
| 44 | import type { MaybeRefOrGetter } from 'vue' |
| 45 | |
| 46 | export function useDocumentTitle(title: MaybeRefOrGetter<string>) { |
| 47 | watch(toRef(title), (t) => { |
| 48 | document.title = t |
| 49 | }, { immediate: true }) |
| 50 | } |
| 51 | ``` |
| 52 | |
| 53 | Adaptable `useCounter` Composable: two-way writable count parameter |
| 54 | |
| 55 | ```ts |
| 56 | import { watch, toRef } from 'vue' |
| 57 | import type { MaybeRef } from 'vue' |
| 58 | |
| 59 | function useCounter(count: MaybeRef<number>) { |
| 60 | const countRef = toRef(count) |
| 61 | function add() { |
| 62 | countRef.value++ |
| 63 | } |
| 64 | return { add } |
| 65 | } |
| 66 | ``` |