v-model на компоненте и provide/inject
Делаем собственный компонент совместимым с v-model и узнаём, как прокидывать данные глубоко в дерево без цепочки пропсов — через provide/inject.
defineModel позволяет вашему компоненту работать с
v-model, как нативныйinput. provide/inject передаёт данные от предка любому потомку напрямую, минуя промежуточные компоненты.
v-model на своём компоненте
В разделе 2 v-model работал на <input>. Свой компонент тоже может его поддерживать. В Vue 3.4+ для этого есть макрос defineModel — он создаёт реактивную привязку, синхронизированную с родителем:
<!-- MyInput.vue -->
<template>
<input :value="model" @input="model = $event.target.value">
</template>
<script setup>
const model = defineModel()
</script>
Теперь родитель использует компонент с v-model, как обычное поле:
<!-- App.vue -->
<template>
<MyInput v-model="name" />
<p>Вы ввели: {{ name }}</p>
</template>
<script setup>
import { ref } from 'vue'
import MyInput from './components/MyInput.vue'
const name = ref('')
</script>
model — это специальный ref: меняешь его внутри компонента — обновляется name у родителя, и наоборот. Двусторонняя связь без ручного emit.
Проблема «бурения пропсов»
Иногда данные (например, тема оформления или текущий пользователь) нужны глубоко вложенному компоненту. Передавать их пропсами через каждый промежуточный уровень утомительно — это называют «props drilling» (бурение пропсов). Промежуточные компоненты вынуждены тащить чужой проп.
provide / inject
provide в предке «публикует» значение, а inject в любом потомке его получает — сколько бы уровней между ними ни было:
<!-- App.vue (предок) -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
</script>
<!-- DeepButton.vue (потомок на любой глубине) -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme') // 'dark', промежуточные уровни не нужны
</script>
Модель «провайд по ключу» на JS
Идея проста: предок кладёт значение в общее хранилище по ключу, потомок достаёт по тому же ключу. Промоделируем:
const context = new Map();
function provide(key, value) { context.set(key, value); }
function inject(key) { return context.get(key); }
// Предок публикует
provide("theme", "dark");
provide("user", "Аня");
// Потомок на любой глубине достаёт — без передачи через промежуточные уровни
console.log("Тема:", inject("theme"));
console.log("Пользователь:", inject("user"));
console.log("Неизвестный ключ:", inject("lang"));
Вывод:
Тема: dark Пользователь: Аня Неизвестный ключ: undefined
Когда что применять
| Задача | Инструмент |
| Передать данные на 1 уровень вниз | пропсы |
| Двусторонняя привязка к компоненту | v-model + defineModel |
| Прокинуть данные глубоко без цепочки пропсов | provide / inject |
| Глобальное состояние всего приложения | Pinia (раздел 6) |
Итог
defineModelделает компонент совместимым сv-modelдля двусторонней связи.- «Props drilling» — передача пропа через много уровней лишь чтобы донести его вглубь.
provideпубликует значение в предке,injectполучает его в любом потомке.- Для глобального состояния приложения лучше Pinia, а не provide/inject.