Как работают provide/inject?

В Vue provide и inject — это механизм, с помощью которого можно передавать зависимости от родительского компонента к его потомкам на любом уровне вложенности, минуя промежуточные компоненты. Это удобно для управления глобальными данными, сервисами или конфигурацией в компонентной иерархии без явной передачи через props.

Общая идея

  • provide — используется в родительском компоненте, чтобы предоставить значение (или значения).

  • inject — используется в потомке, чтобы получить доступ к предоставленным данным.

Этот механизм работает аналогично Dependency Injection (внедрение зависимостей).

Когда использовать

  • Когда нужно передать данные глубоко вниз по иерархии, но не хочется пробрасывать props через несколько уровней.

  • Для сервисов (например, логгер, локализация, хранилище состояния, глобальные настройки).

  • При создании библиотек компонентов, чтобы внутренняя реализация могла взаимодействовать с контекстом.

Использование в Options API

Родительский компонент:

export default {
provide() {
return {
theme: 'dark',
user: { name: 'Анна' }
}
}
}

Потомок:

export default {
inject: \['theme', 'user'\],
mounted() {
console.log(this.theme) // 'dark'
console.log(this.user.name) // 'Анна'
}
}

Использование в Composition API

Родитель:

import { provide } from 'vue'
setup() {
const config = { locale: 'ru', darkMode: true }
provide('config', config)
}

Потомок:

import { inject } from 'vue'
setup() {
const config = inject('config')
return { config }
}

Значения реактивности

По умолчанию provide/inject не делает значения реактивными, если вы передаёте обычные объекты или примитивы.

Чтобы сделать передаваемое значение реактивным, используйте ref() или reactive():

Родитель:

import { provide, ref } from 'vue'
setup() {
const count = ref(0)
provide('count', count)
}

Потомок:

import { inject } from 'vue'
setup() {
const count = inject('count')
return { count } // count.value доступен и реактивен
}

Если передаётся обычное значение ('dark' или { name: 'Анна' }), то изменение родительского значения не обновит его в потомке. Нужно использовать ref или reactive для реактивности.

Указание значения по умолчанию

Если inject не находит переданного значения, можно указать значение по умолчанию:

const theme = inject('theme', 'light') // если не найдено  будет 'light'

Также можно использовать функцию по умолчанию:

const theme = inject('theme', () => 'light', true)

Параметр true означает, что значение по умолчанию будет результатом вызова функции, а не сама функция.

Передача нескольких значений

Можно предоставить несколько значений сразу, возвращая объект:

provide('appContext', {
locale: ref('ru'),
currency: 'USD'
})

И в потомке:

const context = inject('appContext')
console.log(context.locale.value) // 'ru'

Пример с контекстом темы

Родитель:

import { provide, ref } from 'vue'
setup() {
const theme = ref('light')
provide('theme', theme)
setTimeout(() => {
theme.value = 'dark'
}, 3000)
return {}
}

Дочерний:

import { inject } from 'vue'
setup() {
const theme = inject('theme')
return { theme }
}
<template>
<p>Текущая тема: {{ theme }}</p>
</template>

Через 3 секунды текст обновится, потому что theme реактивный (ref).

Особенности и ограничения

  • provide работает только вниз по иерархии. Потомок не может передать данные родителю.

  • inject работает только для потомков компонентов. Вы не можете использовать inject вне компонента.

  • Если потомок не находит значение — и значение по умолчанию не указано — результатом будет undefined.

Часто встречающиеся кейсы

1. Передача глобальной конфигурации

provide('config', {
lang: ref('en'),
currency: 'USD'
})

2. Внутреннее взаимодействие в библиотеке компонентов

Внутри библиотеки (например, UI-библиотека) можно использовать provide/inject для координации между родителем и дочерними частями (например, Tabs, TabItem).

Аналогия с React Context

Механизм похож на React Context API — родитель предоставляет значение, а потомки его читают. Отличие: в Vue это проще и легче использовать.

provide и inject — это мощный способ организации связи между компонентами, особенно когда они удалены друг от друга в иерархии. Это снижает связанность, упрощает поддержку и делает код чище в сложных интерфейсах.