--- title: Use InjectionKey for Type-Safe Provide/Inject impact: MEDIUM impactDescription: Without InjectionKey, injected values are typed as unknown requiring manual type assertions type: best-practice tags: [vue3, typescript, provide-inject, injection-key, composition-api] --- # Use InjectionKey for Type-Safe Provide/Inject **Impact: MEDIUM** - When using `provide/inject` with TypeScript, use `InjectionKey` with Symbol to achieve type-safe dependency injection. Without it, injected values have `unknown` type or require manual type assertions. ## Task Checklist - [ ] Define injection keys using `Symbol() as InjectionKey` - [ ] Store injection keys in a shared file for provider/consumer access - [ ] Understand that injected values are always `T | undefined` - [ ] Provide default values to remove undefined from the type ## The Problem ```vue ``` ```vue ``` ## The Solution: InjectionKey ```typescript // keys.ts - Shared injection keys file import type { InjectionKey, Ref } from 'vue' export interface User { id: string name: string } export interface AuthContext { user: Ref login: (credentials: Credentials) => Promise logout: () => Promise } // Type-safe injection keys export const userKey: InjectionKey = Symbol('user') export const authKey: InjectionKey = Symbol('auth') ``` ```vue ``` ```vue ``` ## Handling the Undefined Type Injected values are always potentially `undefined` because the provider might not exist: ```vue ``` ## Creating a useInject Composable For cleaner consumer code, create typed composables: ```typescript // composables/useAuth.ts import { inject } from 'vue' import { authKey, type AuthContext } from '@/keys' export function useAuth(): AuthContext { const auth = inject(authKey) if (!auth) { throw new Error( 'useAuth() requires an AuthProvider ancestor. ' + 'Make sure to wrap your component tree with .' ) } return auth } ``` ```vue ``` ## Organizing Injection Keys For larger applications, organize keys by domain: ``` src/ ├── injection-keys/ │ ├── index.ts # Re-exports all keys │ ├── auth.ts # Auth-related keys │ ├── theme.ts # Theme-related keys │ └── i18n.ts # i18n-related keys ``` ```typescript // injection-keys/auth.ts import type { InjectionKey, Ref } from 'vue' export interface AuthState { isAuthenticated: Ref user: Ref } export interface AuthActions { login: (email: string, password: string) => Promise logout: () => Promise refresh: () => Promise } export interface AuthContext extends AuthState, AuthActions {} export const authStateKey: InjectionKey = Symbol('authState') export const authActionsKey: InjectionKey = Symbol('authActions') export const authContextKey: InjectionKey = Symbol('authContext') ``` ```typescript // injection-keys/index.ts export * from './auth' export * from './theme' export * from './i18n' ``` ## Generic Injection Keys For reusable patterns with generics: ```typescript // keys/list.ts import type { InjectionKey, Ref } from 'vue' export interface ListContext { items: Ref selectedItem: Ref selectItem: (item: T) => void } // Factory function for creating typed keys export function createListKey(name: string): InjectionKey> { return Symbol(name) as InjectionKey> } // Usage interface Product { id: string; name: string } export const productListKey = createListKey('productList') ``` ## Best Practices Summary 1. **Always use InjectionKey** for TypeScript projects 2. **Use Symbols** to prevent key collisions 3. **Create composables** like `useAuth()` for clean consumer API 4. **Throw descriptive errors** when required providers are missing 5. **Organize keys** in a shared location accessible to providers and consumers 6. **Provide default values** when the provider is optional ## Reference - [Vue.js TypeScript with Composition API - Provide/Inject](https://vuejs.org/guide/typescript/composition-api.html#typing-provide-inject) - [Vue.js Provide/Inject](https://vuejs.org/guide/components/provide-inject.html)