JAVASCRIPT
Vue 3 Dependency Injection with `provide` and `inject`
Learn to use Vue 3's `provide` and `inject` functions to pass data and functions down a component tree without prop drilling, simplifying state management for deeply nested components.
// src/keys.js (Optional: For type-safe keys in TypeScript or consistency)
import { Symbol } from 'vue';
export const USER_INFO_KEY = Symbol('userInfo');
export const APP_SETTINGS_KEY = Symbol('appSettings');
export const NOTIFICATION_SERVICE_KEY = Symbol('notificationService');
// src/components/AppProvider.vue (Top-level component or wrapper)
<template>
<div class="app-provider">
<slot></slot>
</div>
</template>
<script setup>
import { provide, ref, readonly } from 'vue';
import { USER_INFO_KEY, APP_SETTINGS_KEY, NOTIFICATION_SERVICE_KEY } from '../keys';
// Example user data
const currentUser = ref({
id: 1,
name: 'Alice Smith',
email: '[email protected]',
role: 'admin'
});
// Example settings
const settings = ref({
theme: 'dark',
language: 'en-US',
notificationsEnabled: true
});
// Example service (could be a complex object with methods)
const notificationService = {
show: (message, type = 'info') => {
console.log(`Notification (${type}): ${message}`);
// In a real app, this would trigger a global notification system
},
hide: () => {
console.log('Hide notification');
}
};
// Provide the data and service
provide(USER_INFO_KEY, readonly(currentUser)); // Use readonly to prevent direct mutation
provide(APP_SETTINGS_KEY, settings); // Mutable if needed
provide(NOTIFICATION_SERVICE_KEY, notificationService);
// Optional: Function to update user data
const updateUserName = (newName) => {
currentUser.value.name = newName;
};
provide('updateUserName', updateUserName); // Can provide functions too
</script>
// src/components/DeeplyNestedComponent.vue
<template>
<div class="nested-component">
<h3>Deeply Nested Component</h3>
<p>User: {{ userInfo?.name }} (Role: {{ userInfo?.role }})</p>
<p>Theme: {{ appSettings?.theme }}</p>
<button @click="showInfo">Show Notification</button>
<button @click="updateUser">Update User Name</button>
<GrandChildComponent />
</div>
</template>
<script setup>
import { inject } from 'vue';
import { USER_INFO_KEY, APP_SETTINGS_KEY, NOTIFICATION_SERVICE_KEY } from '../keys';
import GrandChildComponent from './GrandChildComponent.vue';
const userInfo = inject(USER_INFO_KEY);
const appSettings = inject(APP_SETTINGS_KEY);
const notificationService = inject(NOTIFICATION_SERVICE_KEY);
const updateUserName = inject('updateUserName'); // Injecting a provided function
const showInfo = () => {
notificationService.show(`Hello, ${userInfo.value.name}!`, 'success');
};
const updateUser = () => {
if (updateUserName) {
updateUserName('Bob Johnson');
console.log('User name updated via injected function.');
}
};
</script>
// src/components/GrandChildComponent.vue
<template>
<div class="grandchild-component">
<h4>Grandchild Component</h4>
<p>Current Language: {{ appSettings?.language }}</p>
</div>
</template>
<script setup>
import { inject } from 'vue';
import { APP_SETTINGS_KEY } from '../keys';
const appSettings = inject(APP_SETTINGS_KEY, { language: 'default' }); // With default value
</script>
// src/App.vue (Root usage)
/*
<template>
<AppProvider>
<h1>My Vue App</h1>
<DeeplyNestedComponent />
</AppProvider>
</template>
<script setup>
import AppProvider from './components/AppProvider.vue';
import DeeplyNestedComponent from './components/DeeplyNestedComponent.vue';
</script>
*/
How it works: This snippet demonstrates how Vue 3's `provide` and `inject` functions enable dependency injection. A `AppProvider` component uses `provide` to make `currentUser` (readonly), `settings`, and a `notificationService` available to all its descendants, no matter how deeply nested. Components like `DeeplyNestedComponent` and `GrandChildComponent` then use `inject` to consume these provided values without having to pass them down through props at each level. Using `Symbol`s as keys helps avoid naming collisions and provides better type safety, especially in TypeScript.