provide() / inject(): Dependency Injection & Readonly Reactive Wrappers
- Last updated
- Prerequisites:
- Vue 3 ref() and reactive()
- Component hierarchy
- vue
- provide
- inject
- dependency-injection
- readonly
- reactivity
Read at your depth
The practical view
`provide(key, value)` allows an ancestor component to share data with all its descendants without prop drilling. Any child component calls `inject(key, defaultValue)` to read the value. If you pass a `ref()` or `reactive()` object to `provide`, changes made in the parent remain reactive in the child. To prevent children from mutating the shared state directly, wrap it in `readonly(state)`.
The same idea in other frameworks
`createContext()` + `useContext()` (triggers consumer re-renders).Hierarchical Dependency Injection via `@Injectable()` and `providers: []`.`provide(key, ref)` + `inject(key)` with prototype chain resolution.Svelte `setContext(key, val)` + `getContext(key)`.Legacy vs modern
Deep Prop Drilling vs provide/inject with readonly Protection
Passing props through intermediate layers is fragile; `provide`/`inject` with `readonly` guarantees clean dependency injection.
<Child :theme="theme" :user="user" /> // ❌ Passed through 4 intermediate layersprovide('theme', { theme: readonly(theme), toggleTheme }); // ✅ NEW: Direct access with mutation safetyInterview gotchas
Context
Interviewers test your knowledge of unidirectional data flow and state architecture.
The mechanical answer
If a raw mutable `ref` is provided, any deeply nested child component can mutate `theme.value = 'light'`, making it extremely difficult to track which component caused state bugs. Wrapping the ref in `readonly(theme)` and providing an explicit updater function (e.g. `toggleTheme`) ensures that state mutations remain owned and debuggable solely by the provider component.
Trap
Do not pass plain primitive values `provide('theme', theme.value)` if you want reactivity; pass the `ref` itself (or `readonly(ref)`).
Context
Checks TypeScript tooling expertise with Vue 3.
The mechanical answer
Use Vue's **`InjectionKey<T>`** utility with a `Symbol`: `export const ThemeKey: InjectionKey<Ref<string>> = Symbol('theme')`. When providing (`provide(ThemeKey, theme)`) and injecting (`const theme = inject(ThemeKey)`), TypeScript automatically infers the exact type `Ref<string>` without manual type casting.
Trap
Using raw string keys `provide('theme', ...)` loses TypeScript type inference and risks collisions across third-party plugins.