Когда компонент глубоко вложен в дерево, передача данных через props на каждом уровне превращается в **props drilling** — громоздкий и хрупкий паттерн:
App → Layout → Sidebar → UserMenu → AvatarКаждый промежуточный компонент вынужден принимать и передавать props, которые ему самому не нужны.
Vue предлагает встроенный механизм — родительский компонент **предоставляет** данные (provide), а любой потомок на любой глубине может их **получить** (inject).
// Родительский компонент
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
provide('userConfig', { locale: 'ru', fontSize: 16 })// Любой дочерний компонент — даже на 5 уровней глубже
import { inject } from 'vue'
// inject(ключ, значение_по_умолчанию)
const theme = inject('theme', ref('light'))
const userConfig = inject('userConfig')
console.log(theme.value) // 'dark'Строковые ключи могут конфликтовать в больших приложениях. Лучшая практика — использовать Symbol:
// keys.js — один файл для всех ключей
export const THEME_KEY = Symbol('theme')
export const USER_KEY = Symbol('user')
export const CONFIG_KEY = Symbol('config')// Родитель
import { THEME_KEY } from './keys.js'
provide(THEME_KEY, theme)
// Потомок
import { THEME_KEY } from './keys.js'
const theme = inject(THEME_KEY)Чтобы потомки не могли напрямую мутировать предоставленные данные (нарушая однонаправленный поток):
import { provide, ref, readonly } from 'vue'
const count = ref(0)
// Предоставляем readonly-версию
provide('count', readonly(count))
// Предоставляем метод для изменения
provide('incrementCount', () => {
count.value++ // только родитель управляет состоянием
})| | Vue provide/inject | React Context |
|---|---|---|
| Создание | Вызов provide() в компоненте | createContext(defaultValue) |
| Потребление | inject(key) | useContext(MyContext) |
| Реактивность | Встроена через ref/reactive | Нужен useState/useReducer |
| Типизация | Symbol + TypeScript | createContext с типом |
| Несколько провайдеров | Вложение компонентов | Вложение <Context.Provider> |
Для глобальных зависимостей (i18n, router, pinia) используют provide на уровне приложения:
const app = createApp(App)
app.provide('globalConfig', { apiUrl: 'https://api.example.com' })
app.mount('#app')Dependency Injection через замыкания и Map — аналог provide/inject в чистом JS
// Паттерн Dependency Injection через иерархию контекстов.
// Каждый контекст хранит свои значения и ссылку на родителя.
function createContext(parent = null) {
const store = new Map()
return {
// Зарегистрировать значение
provide(key, value) {
store.set(key, value)
return this
},
// Получить значение — ищем сначала у себя, потом у родителей
inject(key, defaultValue = undefined) {
if (store.has(key)) {
return store.get(key)
}
if (parent) {
return parent.inject(key, defaultValue)
}
return defaultValue
},
// Создать дочерний контекст
createChild() {
return createContext(this)
},
// Отладка
debug() {
const own = {}
store.forEach((v, k) => { own[String(k)] = v })
return { own, hasParent: parent !== null }
}
}
}
// --- Демонстрация ---
// Корневой контекст (аналог app.provide)
const rootCtx = createContext()
rootCtx.provide('theme', 'dark')
rootCtx.provide('locale', 'ru')
rootCtx.provide('apiUrl', 'https://api.example.com')
// Дочерний контекст (аналог компонента-провайдера)
const featureCtx = rootCtx.createChild()
featureCtx.provide('theme', 'light') // переопределяем тему
featureCtx.provide('featureFlag', true)
// Глубоко вложенный потомок (аналог конечного компонента)
const leafCtx = featureCtx.createChild()
console.log('=== Разрешение зависимостей ===')
console.log('theme у leaf:', leafCtx.inject('theme'))
// 'light' — из featureCtx (ближайший родитель, который provide)
console.log('locale у leaf:', leafCtx.inject('locale'))
// 'ru' — из rootCtx (проксируется через featureCtx)
console.log('apiUrl у leaf:', leafCtx.inject('apiUrl'))
// 'https://api.example.com' — из rootCtx
console.log('featureFlag у leaf:', leafCtx.inject('featureFlag'))
// true — из featureCtx
console.log('missing у leaf:', leafCtx.inject('missing', 'default!'))
// 'default!' — не найдено, использует defaultValue
console.log('\n=== Изолированный контекст ===')
console.log('theme у root:', rootCtx.inject('theme'))
// 'dark' — rootCtx не видит переопределение в featureCtx
console.log('\n=== Отладка ===')
console.log('featureCtx.debug():', featureCtx.debug())Когда компонент глубоко вложен в дерево, передача данных через props на каждом уровне превращается в **props drilling** — громоздкий и хрупкий паттерн:
App → Layout → Sidebar → UserMenu → AvatarКаждый промежуточный компонент вынужден принимать и передавать props, которые ему самому не нужны.
Vue предлагает встроенный механизм — родительский компонент **предоставляет** данные (provide), а любой потомок на любой глубине может их **получить** (inject).
// Родительский компонент
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
provide('userConfig', { locale: 'ru', fontSize: 16 })// Любой дочерний компонент — даже на 5 уровней глубже
import { inject } from 'vue'
// inject(ключ, значение_по_умолчанию)
const theme = inject('theme', ref('light'))
const userConfig = inject('userConfig')
console.log(theme.value) // 'dark'Строковые ключи могут конфликтовать в больших приложениях. Лучшая практика — использовать Symbol:
// keys.js — один файл для всех ключей
export const THEME_KEY = Symbol('theme')
export const USER_KEY = Symbol('user')
export const CONFIG_KEY = Symbol('config')// Родитель
import { THEME_KEY } from './keys.js'
provide(THEME_KEY, theme)
// Потомок
import { THEME_KEY } from './keys.js'
const theme = inject(THEME_KEY)Чтобы потомки не могли напрямую мутировать предоставленные данные (нарушая однонаправленный поток):
import { provide, ref, readonly } from 'vue'
const count = ref(0)
// Предоставляем readonly-версию
provide('count', readonly(count))
// Предоставляем метод для изменения
provide('incrementCount', () => {
count.value++ // только родитель управляет состоянием
})| | Vue provide/inject | React Context |
|---|---|---|
| Создание | Вызов provide() в компоненте | createContext(defaultValue) |
| Потребление | inject(key) | useContext(MyContext) |
| Реактивность | Встроена через ref/reactive | Нужен useState/useReducer |
| Типизация | Symbol + TypeScript | createContext с типом |
| Несколько провайдеров | Вложение компонентов | Вложение <Context.Provider> |
Для глобальных зависимостей (i18n, router, pinia) используют provide на уровне приложения:
const app = createApp(App)
app.provide('globalConfig', { apiUrl: 'https://api.example.com' })
app.mount('#app')Dependency Injection через замыкания и Map — аналог provide/inject в чистом JS
// Паттерн Dependency Injection через иерархию контекстов.
// Каждый контекст хранит свои значения и ссылку на родителя.
function createContext(parent = null) {
const store = new Map()
return {
// Зарегистрировать значение
provide(key, value) {
store.set(key, value)
return this
},
// Получить значение — ищем сначала у себя, потом у родителей
inject(key, defaultValue = undefined) {
if (store.has(key)) {
return store.get(key)
}
if (parent) {
return parent.inject(key, defaultValue)
}
return defaultValue
},
// Создать дочерний контекст
createChild() {
return createContext(this)
},
// Отладка
debug() {
const own = {}
store.forEach((v, k) => { own[String(k)] = v })
return { own, hasParent: parent !== null }
}
}
}
// --- Демонстрация ---
// Корневой контекст (аналог app.provide)
const rootCtx = createContext()
rootCtx.provide('theme', 'dark')
rootCtx.provide('locale', 'ru')
rootCtx.provide('apiUrl', 'https://api.example.com')
// Дочерний контекст (аналог компонента-провайдера)
const featureCtx = rootCtx.createChild()
featureCtx.provide('theme', 'light') // переопределяем тему
featureCtx.provide('featureFlag', true)
// Глубоко вложенный потомок (аналог конечного компонента)
const leafCtx = featureCtx.createChild()
console.log('=== Разрешение зависимостей ===')
console.log('theme у leaf:', leafCtx.inject('theme'))
// 'light' — из featureCtx (ближайший родитель, который provide)
console.log('locale у leaf:', leafCtx.inject('locale'))
// 'ru' — из rootCtx (проксируется через featureCtx)
console.log('apiUrl у leaf:', leafCtx.inject('apiUrl'))
// 'https://api.example.com' — из rootCtx
console.log('featureFlag у leaf:', leafCtx.inject('featureFlag'))
// true — из featureCtx
console.log('missing у leaf:', leafCtx.inject('missing', 'default!'))
// 'default!' — не найдено, использует defaultValue
console.log('\n=== Изолированный контекст ===')
console.log('theme у root:', rootCtx.inject('theme'))
// 'dark' — rootCtx не видит переопределение в featureCtx
console.log('\n=== Отладка ===')
console.log('featureCtx.debug():', featureCtx.debug())Реализуй функцию `createContext()` — возвращает объект с методами `provide(key, value)` и `inject(key, defaultValue)`. Также реализуй `createChildContext(parent)` — создаёт дочерний контекст, который при `inject` ищет значение сначала у себя, затем рекурсивно у родителя. Если значение не найдено нигде — возвращает `defaultValue`.
В createChildContext создай новый контекст через createContext(), но метод inject оберни — сначала проверяй собственный Map, и только если не нашёл, вызывай parent.inject(key, defaultValue). Можно сохранить оригинальный inject и делегировать ему.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке