Vue: props drilling, использование provide inject Печать
Добавил(а) microsin   

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, быстрый старт.