Provide / Inject
Эта страница предполагает, что вы уже прочитали Основы компонентов.
Сквозная передача пропсов
Обычно, когда нам нужно передать данные от родительского компонента к дочернему, мы используем пропсы. Однако представьте себе случай, когда у нас большое дерево компонентов, и глубоко вложенному компоненту нужно что-то от компонента дальнего предка. При использовании только пропсов нам пришлось бы передавать один и тот же проп по всей родительской цепочке:
Обратите внимание, что хотя компонент <Footer>
может вообще не заботиться об этих пропсах, ему всё равно нужно объявить и передать их, чтобы <DeepChild>
мог получить к ним доступ. Если родительская цепочка длиннее, то на пути к ней будет затронуто больше компонентов. Это называется сквозной передачей. и с ним определённо не очень весело иметь дело.
Мы можем решить проблему сквозной передачи пропсов с помощью provide
и inject
. Родительский компонент может служить провайдером зависимостей для всех своих потомков. Любой компонент в дереве-потомке, независимо от его глубины, может инжектировать зависимости, предоставленные компонентами, расположенными выше в его родительской цепочке.
Provide
Чтобы предоставить данные потомкам компонента, используйте функцию provide()
:
vue
<script setup>
import { provide } from 'vue'
provide(/* ключ */ 'message', /* значение */ 'hello!')
</script>
Если вы не используете <script setup>
, убедитесь, что provide()
вызывается синхронно внутри setup()
:
js
import { provide } from 'vue'
export default {
setup() {
provide(/* ключ */ 'message', /* значение */ 'hello!')
}
}
Функция provide()
принимает два аргумента. Первый аргумент называется ключ инъекции, который может быть строкой или символом
. Ключ инъекции используется компонентами-потомками для поиска нужного значения для инъекции. Один компонент может вызывать provide()
несколько раз с разными ключами инъекции для предоставления различных значений.
Второй аргумент — это предоставленное значение. Значение может быть любого типа, включая реактивное состояние, такое как refs:
js
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)
Предоставление реактивных значений позволяет компонентам-потомкам, использующим предоставленное значение, устанавливать реактивное соединение с компонентом-провайдером.
Provide на уровне приложения
Помимо предоставления данных в компоненте, мы также можем предоставлять их на уровне приложения:
js
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* ключ */ 'message', /* значение */ 'hello!')
Предоставления на уровне приложения доступны для всех компонентов, отображаемых в приложении. Это особенно полезно при написании плагинов, так как плагины обычно не могут предоставлять значения с помощью компонентов.
Inject
Чтобы внедрить данные, предоставленные компонентом-предком, используйте функцию inject()
:
vue
<script setup>
import { inject } from 'vue'
const message = inject('message')
</script>
Если несколько родительских компонентов предоставляют данные с одинаковым ключом, функция inject
выберет значение от ближайшего родителя в цепочке родительских компонентов.
Если предоставленное значение является ссылкой, оно будет инжектировано как есть и не будет автоматически разворачиваться. Это позволяет компоненту-инжектору сохранять реактивную связь с компонентом-провайдером.
Полный пример provide + inject с реактивностью
Опять же, если не используется <script setup>
, inject()
следует вызывать синхронно только внутри setup()
:
js
import { inject } from 'vue'
export default {
setup() {
const message = inject('message')
return { message }
}
}
Значения по умолчанию для инъекций
По умолчанию inject
предполагает, что инжектируемый ключ предоставляется где-то в родительской цепочке. В случае, если ключ не указан, будет выдано предупреждение во время выполнения.
Если мы хотим, чтобы инжектируемое свойство работало с необязательными провайдерами, нам нужно объявить значение по умолчанию, аналогично props:
js
// `value` будет содержать "значение по умолчанию"
// если не было предоставлено данных, соответствующих «сообщению»
const value = inject('сообщение', 'значение по умолчанию')
В некоторых случаях значение по умолчанию может потребоваться создать путём вызова функции или инстанцирования нового класса. Чтобы избежать лишних вычислений или побочных эффектов в случае, если необязательное значение не используется, мы можем использовать фабричную функцию для создания значения по умолчанию:
js
const value = inject('key', () => new ExpensiveClass(), true)
Третий параметр указывает, что значение по умолчанию должно рассматриваться как фабричная функция.
Работа с реактивностью
При использовании реактивного предоставления / инъекции значений, рекомендуется держать любые мутации реактивного состояния внутри провайдера, когда это возможно. Это гарантирует, что предоставленное состояние и его возможные мутации будут находиться в одном компоненте, что упрощает их поддержку в будущем.
Бывают случаи, когда нам необходимо обновить данные компонента-инжектора. В таких случаях мы рекомендуем предоставлять функцию, которая отвечает за изменение состояния:
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
<!-- в компоненте инжектора -->
<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>
Работа с символьными клавишами
До сих пор в примерах мы использовали ключи для инъекции строк. Если вы работаете в большом приложении с большим количеством поставщиков зависимостей или создаете компоненты, которые будут использоваться другими разработчиками, лучше всего использовать ключи инъекции Symbol, чтобы избежать возможных коллизий.
Рекомендуется экспортировать символы в отдельный файл:
js
// keys.js
export const myInjectionKey = Symbol()
js
// в компоненте провайдера
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* данные для предоставления */
})
js
// в компоненте инжектора
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
См. также: Типизация Provide / Inject