Vue docs, chapter by chapter

Reusability — composables, directives, plugins

The Composables chapter's conventions and restrictions, custom directive hooks and the binding object, and how plugins install through app.use.

The Reusability chapters describe the three extension points Vue gives you and, more importantly, when each one is appropriate. The guide is explicit that composables are the default, directives are for low-level DOM access only, and plugins are for app-level installation. Knowing that hierarchy is a frequent design-question answer.

Know — Composables

  • Definition: a function that leverages the Composition API to encapsulate and reuse stateful logic. Stateless helpers (formatting) are utilities, not composables.
  • Conventions: name with use; accept refs, getters or plain values and normalise them with toValue(); return a plain object of refs so callers can destructure; if a caller wants an object, they wrap it in reactive().
  • Side effects: do DOM work in onMounted, clean up in onUnmounted (or onScopeDispose); guard for SSR. The guide's useEventListener and useMouse examples show the pattern; useFetch shows reactive inputs with watchEffect refetching when a getter-based URL changes.
  • Usage restrictions: call composables synchronously in <script setup>, setup(), or another composable, or in lifecycle hooks; the exception for <script setup> is that after await the instance context is restored automatically. app.runWithContext() lets you call them outside a component.
  • Extraction is also for code organisation within one component: split a large component into useFeatureA(), useFeatureB().
  • Compared with: mixins (unclear sources, name collisions, implicit cross-mixin communication), renderless components (extra instance cost), React hooks (Vue setup runs once; no stale closures or dependency arrays).

Know — Custom Directives

  • Definition: in <script setup> any camelCase variable starting with v is usable as a directive (const vFocus = { mounted: el => el.focus() } → v-focus); otherwise the directives option or app.directive('focus', …).
  • Hooks: created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted, each receiving (el, binding, vnode, prevVnode).
  • binding: value, oldValue (only in beforeUpdate/updated), arg (v-dir:arg), modifiers ({ foo: true }), instance, dir. Directives may receive object literals as values.
  • Function shorthand: passing a function registers it for both mounted and updated.
  • On components: discouraged; the directive applies to the root node, and with multiple roots it is ignored with a warning. The guide's rule: use directives only when the desired behaviour can only be achieved through direct DOM manipulation; prefer v-bind and built-ins.
  • SSR: a directive can implement getSSRProps(binding, vnode) to render attributes on the server.

Know — Plugins

  • Shape: an object with install(app, options) or a function (app, options) => {}, installed with app.use(plugin, options).
  • Typical work: app.component/app.directive for globals, app.provide for injectable resources, app.config.globalProperties for this.$x in Options API, plus anything with a side effect (a router, a store).
  • The guide's i18n example: app.config.globalProperties.$translate reading dot paths from options, and app.provide('i18n', options) so Composition API code can inject('i18n').

How it works

app/composables/useEventListener.ts
export function useEventListener<K extends keyof WindowEventMap>(
  target: MaybeRefOrGetter<Window | HTMLElement | null>,
  event: K,
  handler: (e: WindowEventMap[K]) => void,
) {
  // Mount/unmount pairing as the guide shows; toValue accepts a value, a ref or a getter.
  onMounted(() => toValue(target)?.addEventListener(event, handler as EventListener))
  onUnmounted(() => toValue(target)?.removeEventListener(event, handler as EventListener))
}
app/components/Docs/Autofocus.vue
<script setup lang="ts">
import type { Directive } from 'vue'

// Object form with the binding fields the docs list. v-autofocus:soft.select="delayMs"
const vAutofocus: Directive<HTMLInputElement, number> = {
  mounted(el, { value, arg, modifiers }) {
    setTimeout(() => {
      if (arg === 'soft' && document.activeElement !== document.body) return
      el.focus()
      if (modifiers.select) el.select()
    }, value ?? 0)
  },
  getSSRProps: () => ({ 'data-autofocus': '' }), // rendered by the server
}
</script>

<template>
  <input v-autofocus:soft.select="50">
</template>
app/plugins/i18n-lite.ts (plain Vue plugin)
import type { App } from 'vue'

export const i18nLite = {
  install(app: App, options: Record<string, unknown>) {
    app.config.globalProperties.$translate = (key: string) =>
      key.split('.').reduce<unknown>((o, k) => (o as Record<string, unknown>)?.[k], options) as string
    app.provide('i18n', options) // Composition API side: inject('i18n')
  },
}
// app.use(i18nLite, { greetings: { hello: 'Ahoj' } })
Gotcha· A directive on a component

<MyComponent v-tooltip="text"> applies the directive to the component's root element, silently, and if the component has multiple roots the directive is dropped with a warning. The guide recommends props for component behaviour and directives only for raw DOM work.

Exercise

Exercise
  • Write useMouse() from the guide with useEventListener, then make the target a ref to an element instead of window.
  • Write a directive with all seven hooks logging their names and a bound value; toggle the value and the element's v-if and record the order.
  • Install the plugin above and use $translate in an Options API component and inject('i18n') in a <script setup> one.

Be able to say

Be able to say· Composable, custom directive or plugin: how do you decide?

"The guide's hierarchy: a composable for any stateful logic that components share, because it is explicit, typed and testable; a custom directive only when the behaviour is inherently about manipulating a DOM element, like focusing, measuring or attaching a third-party widget, and even then not on components with several roots; a plugin when something has to be installed at the application level, such as registering global components, providing a resource with app.provide, or adding globalProperties for Options API code. Most 'I need a directive' cases are really a composable with a template ref."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.