|
provide и inject - это механизм передачи данных от родительского компонента к дочерним компонентам, который позволяет избежать "prop drilling" (пробрасывания пропсов через несколько уровней вложенности).
[Основные концепции]
Что такое provide/inject?
provide - функция/опция, которая предоставляет данные для всех потомков inject - функция/опция, которая получает данные от предков
Этот механизм работает независимо от глубины иерархии компонентов.
[Синтаксис]
Composition API:
// Родительский компонент (Provider) import { provide, ref } from 'vue'
export default { setup() { const user = ref({ name: 'John', role: 'admin' }) const theme = ref('dark')
provide('user', user) provide('theme', theme)
return {} }
}
// Дочерний компонент (Injector) import { inject } from 'vue'
export default { setup() { const user = inject('user') const theme = inject('theme', 'light') // со значением по умолчанию
return { user, theme } }
}
Options API:
// Родительский компонент export default { provide() { return { user: this.user, theme: this.theme } }, data() { return { user: { name: 'John', role: 'admin' }, theme: 'dark' } }
}
// Дочерний компонент export default { inject: ['user', 'theme'], created() { console.log(this.user) // { name: 'John', role: 'admin' } console.log(this.theme) // 'dark' }
}
[Реактивность]
Composition API (реактивность по умолчанию):
// Provider import { provide, ref } from 'vue'
const count = ref(0)
provide('count', count)
// Функция для обновления const updateCount = () => { count.value++
}
provide('updateCount', updateCount)
Options API (для реактивности используйте computed):
// Provider import { computed } from 'vue'
export default { data() { return { count: 0 } }, provide() { return { count: computed(() => this.count) } }
}
[Продвинутые паттерны]
1. Инъекция с проверкой типа:
// Symbol для уникальности ключа export const injectionKeys = { user: Symbol('user'), theme: Symbol('theme'), api: Symbol('api')
}
// Provider
provide(injectionKeys.user, userData)
// Injector const user = inject(injectionKeys.user)
2. Фабрика зависимостей:
// Provider import { provide, reactive } from 'vue'
const store = reactive({ state: {}, actions: {}
})
provide('store', store)
provide('api', createApi())
3. Composable с provide/inject:
// composables/useTheme.js import { provide, inject, ref } from 'vue'
const ThemeSymbol = Symbol()
export function useThemeProvider() { const theme = ref('light')
const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light' }
provide(ThemeSymbol, { theme, toggleTheme })
}
export function useTheme() { const theme = inject(ThemeSymbol) if (!theme) { throw new Error('useTheme() must be used within a ThemeProvider') } return theme
}
[Сравнение с другими паттернами]
provide/inject vs props:
| provide/inject |
props |
| Для глубоко вложенных компонентов |
Для прямых потомков |
| Неявная передача данных |
Явная передача данных |
| Сложнее отслеживать поток данных |
Легко отслеживать поток данных |
provide/inject vs Vuex/Pinia:
| provide/inject |
Vuex/Pinia |
| Локальное состояние |
Глобальное состояние |
| Для иерархии компонентов |
Для всего приложения |
| Проще для небольших приложений |
Лучше для средних и больших |
[Практические примеры]
1. Тема оформления:
// ThemeProvider.vue
< template>
< div :class="theme">
< slot />
< /div>
< /template>
< script setup> import { provide, ref } from 'vue'
const theme = ref('light')
const toggleTheme = () => { theme.value = theme.value === 'light' ? 'dark' : 'light'
}
provide('theme', { current: theme, toggle: toggleTheme
})
< /script>
2. Сервис аутентификации:
// composables/useAuth.js export function useAuthProvider() { const user = ref(null) const isAuthenticated = computed(() => !!user.value)
const login = async (credentials) => { // логика входа user.value = await api.login(credentials) }
const logout = () => { user.value = null }
provide('auth', { user, isAuthenticated, login, logout })
}
// ProtectedComponent.vue const { user, isAuthenticated, logout } = inject('auth')
3. Конфигурация приложения:
// App.vue import { provide } from 'vue'
const config = { apiUrl: 'https://api.example.com', version: '1.0.0', features: ['feature1', 'feature2']
}
provide('config', config)
[Лучшие практики]
1. Использование Symbol для ключей:
// injectionKeys.js export const INJECTION_KEYS = { THEME: Symbol('theme'), AUTH: Symbol('auth'), CONFIG: Symbol('config')
}
2. Предоставление только необходимого:
// Хорошо: предоставляем только то, что нужно
provide('theme', { current: theme, setTheme: setTheme
})
// Плохо: предоставляем весь объект
provide('theme', themeObject) // где themeObject содержит много лишнего
3. Проверка наличия инъекции:
const theme = inject('theme', null) if (!theme) { console.warn('Theme provider not found') return defaultTheme
}
4. Документирование зависимостей:
/** * @requires provide('theme') - объект с полями current и toggle */ function useThemedComponent() { const theme = inject('theme') // ...
}
[Ограничения и предостережения]
1. Не для глобального состояния: provide/inject предназначен для иерархии компонентов, не для глобального состояния всего приложения. 2. Сложность отладки: труднее отследить источник данных. 3. Потеря реактивности в Options API без computed. 4. Нет реактивности для примитивов в Options API.
[Заключение]
provide/inject - мощный инструмент для:
- Чтобы избежать prop drilling. - Создания плагинов и библиотек. - Реализации паттернов "провайдер-потребитель". - Управления темами и конфигурацией.
Используйте его разумно, комбинируя с другими паттернами управления состоянием для создания масштабируемых приложений.
[Ссылки]
1. Vue 3, быстрый старт. |