Перейти к содержанию

Provide / Inject

Эта страница предполагает, что вы уже прочитали Основы компонентов.

Сквозная передача пропсов

Обычно, когда нам нужно передать данные от родительского компонента к дочернему, мы используем пропсы. Однако представьте себе случай, когда у нас большое дерево компонентов, и глубоко вложенному компоненту нужно что-то от компонента дальнего предка. При использовании только пропсов нам пришлось бы передавать один и тот же проп по всей родительской цепочке:

диаграмма сквозной передачи пропсов

Обратите внимание, что хотя компонент <Footer> может вообще не заботиться об этих пропсах, ему всё равно нужно объявить и передать их, чтобы <DeepChild> мог получить к ним доступ. Если родительская цепочка длиннее, то на пути к ней будет затронуто больше компонентов. Это называется сквозной передачей. и с ним определённо не очень весело иметь дело.

Мы можем решить проблему сквозной передачи пропсов с помощью provide и inject. Родительский компонент может служить провайдером зависимостей для всех своих потомков. Любой компонент в дереве-потомке, независимо от его глубины, может инжектировать зависимости, предоставленные компонентами, расположенными выше в его родительской цепочке.

Схема 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
export default {
  provide: {
    message: 'привет!'
  }
}

Для каждого свойства в объекте provide ключ используется дочерними компонентами для поиска нужного значения для инъекции, а значение — это то, что в итоге будет инъецировано.

Если нам нужно предоставить состояние экземпляра, например, данные, объявленные через data(), то provide должен использовать значение функции:

js
export default {
  data() {
    return {
      message: 'привет!'
    }
  },
  provide() {
    // используем синтаксис функции, чтобы получить доступ к `this`.
    return {
      message: this.message
    }
  }
}

Однако обратите внимание, что это не делает инъекцию реактивной. О том, как сделать инъекции реактивными, мы поговорим ниже.

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:

js
export default {
  inject: ['message'],
  created() {
    console.log(this.message) // инжектированное значение
  }
}

Инъекции разрешаются перед собственным состоянием компонента, поэтому вы можете получить доступ к инжектированным свойствам в data():

js
export default {
  inject: ['message'],
  data() {
    return {
      // исходные данные на основе инжектированного значения
      fullMessage: this.message
    }
  }
}

Если несколько родительских компонентов предоставляют данные с одинаковым ключом, функция inject выберет значение от ближайшего родителя в цепочке родительских компонентов.

Полный пример provide + inject

Инжектирование псевдонимов

При использовании синтаксиса массива для inject, инжектируемые свойства отображаются на экземпляре компонента по одному и тому же ключу. В приведённом выше примере свойство было предоставлено под ключом "message" и инжектировано как this.message. Локальный ключ — это тот же ключ, что и ключ инъекции.

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

js
export default {
  inject: {
    /* локальный ключ */ localMessage: {
      from: /* инжектируемый ключ */ 'message'
    }
  }
}

Здесь компонент найдет свойство с ключом "message", а затем выставит его как this.localMessage.

Значения по умолчанию для инъекций

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

Если мы хотим, чтобы инжектируемое свойство работало с необязательными провайдерами, нам нужно объявить значение по умолчанию, аналогично props:

js
// `value` будет содержать "значение по умолчанию"
// если не было предоставлено данных, соответствующих «сообщению»
const value = inject('сообщение', 'значение по умолчанию')

В некоторых случаях значение по умолчанию может потребоваться создать путём вызова функции или инстанцирования нового класса. Чтобы избежать лишних вычислений или побочных эффектов в случае, если необязательное значение не используется, мы можем использовать фабричную функцию для создания значения по умолчанию:

js
const value = inject('key', () => new ExpensiveClass(), true)

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

js
export default {
  // Синтаксис объекта необходим при объявлении значений по умолчанию для инъекций
  inject: {
    message: {
      from: 'message', // это необязательно, если вы используете один и тот же ключ для инъекций
      default: 'default value'
    },
    user: {
      // используйте фабричную функцию для непервичных значений, которые дорого создавать,
      // или для значений, которые должны быть уникальными для каждого экземпляра компонента.
      default: () => ({ name: 'John' })
    }
  }
}

Работа с реактивностью

При использовании реактивного предоставления / инъекции значений, рекомендуется держать любые мутации реактивного состояния внутри провайдера, когда это возможно. Это гарантирует, что предоставленное состояние и его возможные мутации будут находиться в одном компоненте, что упрощает их поддержку в будущем.

Бывают случаи, когда нам необходимо обновить данные компонента-инжектора. В таких случаях мы рекомендуем предоставлять функцию, которая отвечает за изменение состояния:

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>

Чтобы инъекции были реактивно связаны с провайдером, нам нужно предоставить вычисляемое свойство с помощью функции computed():

js
import { computed } from 'vue'

export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    return {
      // явно предоставляем вычисляемое свойство
      message: computed(() => this.message)
    }
  }
}

Полный пример provide + inject с реактивностью

Функция computed() обычно используется в компонентах Composition API, но также может быть использована для дополнения некоторых случаев использования в Options API. Вы можете узнать больше о его использовании, прочитав Основы реактивности и Вычисляемые свойства переключив стиль API на Composition API.

Работа с символьными клавишами

До сих пор в примерах мы использовали ключи для инъекции строк. Если вы работаете в большом приложении с большим количеством поставщиков зависимостей или создаете компоненты, которые будут использоваться другими разработчиками, лучше всего использовать ключи инъекции 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

js
// в компоненте провайдера
import { myInjectionKey } from './keys.js'

export default {
  provide() {
    return {
      [myInjectionKey]: {
        /* данные для предоставления */
      }
    }
  }
}
js
// в компоненте инжектора
import { myInjectionKey } from './keys.js'

export default {
  inject: {
    injected: { from: myInjectionKey }
  }
}
Provide / Inject