Provide / Inject
Подразумевается, что вы уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.
Пробрасывание входных параметров
Обычно для передачи данных от родительского компонента в дочерний используются входные параметры. Представьте структуру, в которой будет несколько глубоко вложенных компонентов и потребуется что-то от родительского компонента в глубоко вложенном дочернем. В таком случае необходимо передавать входные параметры вниз по всей цепочке компонентов, что может быть очень неудобным.
Обратите внимание, хотя компоненту <Footer>
не нужны входные параметры, ему все равно необходимо объявить и передать их, чтобы <DeepChild>
мог получить к ним доступ. Если есть более длинная родительская цепочка, по пути будет затронуто больше компонентов. Это называется "пробрасывание входных параметров", и с этим определенно не весело иметь дело.
В таких случаях можно использовать пару provide
и inject
. Родительские компоненты могут служить провайдерами зависимостей для всех своих потомков. Любой компонент в дереве-потомке, независимо от его глубины, может внедрить зависимости, предоставляемые компонентами, расположенными выше в его родительской цепочке.
Provide
Чтобы предоставить данные потомкам компонента, используйте функцию provide()
:
vue
<script setup>
import { provide } from 'vue'
provide(/* ключ */ 'message', /* значение */ 'привет!')
</script>
Если не используется <script setup>
, убедитесь, что функция provide()
вызывается синхронно внутри функции setup()
:
js
import { provide } from 'vue'
export default {
setup() {
provide(/* ключ */ 'message', /* значение */ 'привет!')
}
}
Функция provide()
принимает два аргумента. Первый аргумент называется ключом инъекции, который может быть строкой или Symbol
. Ключ инъекции используется компонентами-потомками для поиска нужного значения для инъекции. Один компонент может вызывать функцию provide()
несколько раз с разными ключами инъекции для получения различных значений.
Вторым аргументом является предоставляемое значение. Значение может быть любого типа, включая реактивное состояние, такое как refs:
js
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)
Предоставление реактивных значений позволяет компонентам-потомкам, использующим предоставленное значение, устанавливать реактивное соединение с компонентом-провайдером.
Предоставление на уровне приложения
Помимо предоставления данных в компоненте, мы также можем предоставлять их на уровне приложения:
js
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* ключ */ 'message', /* значение */ 'привет!')
Предоставление на уровне приложения доступно для всех компонентов, отображаемых в приложении. Это особенно полезно при написании плагинов, поскольку плагины обычно не могут предоставлять значения с использованием компонентов.
Inject
Для инъекции данных, предоставляемых компонентом-предком, используйте функцию inject()
:
vue
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
Если предоставленное значение является ref, то оно будет внедрено как есть и не будет автоматически разворачиваться. Это позволяет компоненту-инжектору сохранять реактивную связь с компонентом-провайдером.
Полный пример provide + inject с реактивностью
Опять же, если не используется <script setup>
, то inject()
следует вызывать синхронно только внутри setup()
:
js
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}
Значения по умолчанию для инъекций
По умолчанию, inject
предполагает, что инжектируемый ключ предоставлен где-то в родительской цепочке. В случае, если ключ не предоставлен, будет выдано предупреждение.
Если мы хотим, чтобы инжектируемое свойство работало с необязательными провайдерами, нам необходимо объявить значение по умолчанию, аналогично входным параметрам:
js
// `value` будет "значением по умолчанию"
// если данные, соответствующие "message", не были предоставлены
const value = inject('message', 'default value')
В некоторых случаях значение по умолчанию может потребоваться создать путем вызова функции или инстанцирования нового класса. Чтобы избежать лишних вычислений или побочных эффектов в случае, если необязательное значение не используется, мы можем использовать фабричную функцию для создания значения по умолчанию:
js
const value = inject('key', () => new ExpensiveClass(), true)
Третий параметр указывает, что значение по умолчанию должно рассматриваться как фабричная функция.
Работа с реактивностью
При использовании реактивных значений provide / inject, рекомендуется по возможности хранить любые мутации реактивного состояния внутри провайдера. Это гарантирует, что предоставляемое состояние и его возможные мутации будут находиться в одном компоненте, что облегчает их поддержку в будущем.
Бывают случаи, когда необходимо обновить данные из компонента-инжектора. В таких случаях рекомендуется предоставлять функцию, отвечающую за мутацию состояния:
vue
<!-- внутри компонента провайдер -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
vue
<!-- в компоненте injector -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
Наконец, вы можете обернуть предоставленное значение с помощью readonly()
, если хотите гарантировать, что данные, переданные через provide
, не могут быть изменены инжектируемым компонентом.
vue
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
Работа с символьными ключами
До сих пор в примерах мы использовали ключи инъекции строк. Если вы работаете в большом приложении с большим количеством поставщиков зависимостей или создаете компоненты, которые будут использоваться другими разработчиками, лучше всего использовать ключи инъекции символов, чтобы избежать возможных коллизий.
Рекомендуется экспортировать символы в отдельный файл:
js
// keys.js
export const myInjectionKey = Symbol()
js
// в компоненте provider
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* данные для предоставления */
})
js
// в компоненте injector
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
См. также: Типизация Provide / Inject