963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
4.3 KiB
4.3 KiB
Use Symbol Injection Keys in Plugins
Rule
When using provide/inject in Vue plugins, use Symbol keys (preferably with InjectionKey<T> for TypeScript) instead of string keys to prevent naming collisions and ensure type safety.
Why This Matters
-
Collision prevention: String keys like
'i18n'or'api'can easily collide between multiple plugins or different parts of your application. -
Type safety: TypeScript's
InjectionKey<T>provides automatic type inference when usinginject(). -
Refactoring safety: Symbols are unique, so renaming is safe and explicit.
-
Debugging: Symbols can have descriptive names for debugging while remaining unique.
Bad Practice
// plugin.ts
export default {
install(app) {
// String key - can collide with other plugins!
app.provide('http', axios);
app.provide('config', appConfig);
},
};
// component.vue
const http = inject('http'); // Type is unknown
const config = inject('config'); // Type is unknown
// Another plugin accidentally uses the same key
otherPlugin.install = (app) => {
app.provide('http', differentHttpClient); // COLLISION! Overwrites first
};
Good Practice
// plugins/keys.ts
import type { InjectionKey } from 'vue'
import type { AxiosInstance } from 'axios'
export interface AppConfig {
apiUrl: string
timeout: number
}
// Typed injection keys - unique and type-safe
export const httpKey: InjectionKey<AxiosInstance> = Symbol('http')
export const configKey: InjectionKey<AppConfig> = Symbol('appConfig')
// plugin.ts
import { httpKey, configKey } from './keys'
export default {
install(app) {
app.provide(httpKey, axios)
app.provide(configKey, { apiUrl: '/api', timeout: 5000 })
}
}
// component.vue
<script setup lang="ts">
import { inject } from 'vue'
import { httpKey, configKey } from '@/plugins/keys'
// Fully typed! No 'unknown' type
const http = inject(httpKey) // Type: AxiosInstance | undefined
const config = inject(configKey) // Type: AppConfig | undefined
</script>
Providing Default Values with Type Safety
// With InjectionKey, default values are type-checked
const config = inject(configKey, {
apiUrl: '/default-api',
timeout: 3000,
});
// Type: AppConfig (not undefined because default provided)
// Type error if default doesn't match!
const config = inject(configKey, {
apiUrl: '/api',
// Missing 'timeout' - TypeScript error!
});
Organizing Injection Keys
For larger applications, organize keys by domain:
// injection-keys/index.ts
export * from './auth';
export * from './i18n';
export * from './http';
// injection-keys/auth.ts
import type { InjectionKey } from 'vue';
export interface AuthService {
login: (credentials: Credentials) => Promise<User>;
logout: () => Promise<void>;
currentUser: Ref<User | null>;
}
export const authKey: InjectionKey<AuthService> = Symbol('auth');
// injection-keys/i18n.ts
export interface I18n {
t: (key: string, params?: Record<string, string>) => string;
locale: Ref<string>;
}
export const i18nKey: InjectionKey<I18n> = Symbol('i18n');
Creating a useInject Helper
For cleaner component code, create typed composables:
// composables/useAuth.ts
import { inject } from 'vue'
import { authKey, type AuthService } from '@/injection-keys'
export function useAuth(): AuthService {
const auth = inject(authKey)
if (!auth) {
throw new Error('Auth plugin not installed. Did you forget app.use(authPlugin)?')
}
return auth
}
// component.vue
<script setup>
import { useAuth } from '@/composables/useAuth'
const auth = useAuth() // Type: AuthService (not undefined)
await auth.login(credentials)
</script>
When String Keys Are Acceptable
- Internal plugin use: If both provide and inject are in the same plugin file
- Simple applications: Very small apps with no collision risk
- Dynamic keys: When the key name must be computed at runtime
Even then, consider using a namespaced string:
// Better than plain 'config'
app.provide('myPlugin:config', config);