Skip to content

Provide / Inject во Vue 3

1. Что такое Provide / Inject?

Provide и Inject — механизм внедрения зависимостей в Vue 3, позволяющий передавать данные от родительского компонента к любому вложенному потомку, минуя промежуточные компоненты (без prop drilling).

2. Базовое использование

Provide в родительском компоненте

vue
<script setup>
import { provide, ref } from 'vue';

const theme = ref('dark');

provide('theme', theme);
</script>

<template>
  <div>
    <ChildComponent />
  </div>
</template>

Inject в дочернем компоненте

vue
<script setup>
import { inject } from 'vue';

const theme = inject('theme');
</script>

<template>
  <div :class="theme">
    Текущая тема: {{ theme }}
  </div>
</template>

3. Значения по умолчанию

vue
<script setup>
import { inject } from 'vue';

// Если 'theme' не предоставлен, используется 'light'
const theme = inject('theme', 'light');

// Фабричная функция для значений по умолчанию
const config = inject('config', () => ({
  color: 'blue',
  fontSize: 14,
}));
</script>

4. Типизация с TypeScript

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

typescript
// keys.ts
import { InjectionKey, Ref } from 'vue';

export const themeKey: InjectionKey<Ref<string>> = Symbol('theme');

export interface UserContext {
  name: string;
  role: string;
  logout: () => void;
}

export const userKey: InjectionKey<UserContext> = Symbol('user');

Provide с типизацией

vue
<script setup lang="ts">
import { provide, ref } from 'vue';
import { themeKey, userKey } from './keys';

const theme = ref('dark');
provide(themeKey, theme);

provide(userKey, {
  name: 'Иван',
  role: 'admin',
  logout: () => console.log('Выход'),
});
</script>

Inject с типизацией

vue
<script setup lang="ts">
import { inject } from 'vue';
import { themeKey, userKey } from './keys';

// TypeScript знает тип: Ref<string> | undefined
const theme = inject(themeKey);

// С обязательным значением
const user = inject(userKey);
if (user) {
  console.log(user.name); // типизировано
}
</script>

5. Реактивный Provide / Inject

Передача реактивных данных

vue
<!-- Родительский компонент -->
<script setup>
import { provide, ref, readonly } from 'vue';

const count = ref(0);

function increment() {
  count.value++;
}

// readonly предотвращает изменение данных потомками
provide('count', readonly(count));
provide('increment', increment);
</script>
vue
<!-- Дочерний компонент -->
<script setup>
import { inject } from 'vue';

const count = inject('count');
const increment = inject('increment');
</script>

<template>
  <div>
    <p>Счёт: {{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

6. Provide на уровне приложения

typescript
// main.ts
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

// Доступно во всех компонентах приложения
app.provide('appVersion', '1.0.0');
app.provide('apiUrl', 'https://api.example.com');

app.mount('#app');

7. Паттерн: Composable с Provide / Inject

Создание контекста

typescript
// composables/useTheme.ts
import { provide, inject, ref, readonly, InjectionKey, Ref } from 'vue';

interface ThemeContext {
  theme: Readonly<Ref<string>>;
  toggleTheme: () => void;
}

const ThemeKey: InjectionKey<ThemeContext> = Symbol('ThemeContext');

export function provideTheme() {
  const theme = ref('light');

  function toggleTheme() {
    theme.value = theme.value === 'light' ? 'dark' : 'light';
  }

  const context: ThemeContext = {
    theme: readonly(theme),
    toggleTheme,
  };

  provide(ThemeKey, context);
  return context;
}

export function useTheme(): ThemeContext {
  const context = inject(ThemeKey);
  if (!context) {
    throw new Error('useTheme() вызван без provideTheme()');
  }
  return context;
}

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

vue
<!-- App.vue -->
<script setup>
import { provideTheme } from './composables/useTheme';

provideTheme();
</script>

<!-- Любой вложенный компонент -->
<script setup>
import { useTheme } from './composables/useTheme';

const { theme, toggleTheme } = useTheme();
</script>

<template>
  <button @click="toggleTheme">
    Тема: {{ theme }}
  </button>
</template>

8. Сравнение с другими подходами

ПодходКогда использовать
PropsПрямая передача данных родитель → потомок
EventsОбратная связь потомок → родитель
Provide/InjectПередача данных через несколько уровней
PiniaГлобальное состояние приложения
ComposablesПереиспользуемая логика