Как работают функции provide и inject во Vue 3?

от автора

Релиз третьей версии фреймворка Vue предоставил разработчикам доработанный API для манипуляции состоянием приложения. Во Vue 3 были дополнены инструменты для реализации паттерна Dependency Injection. Использование этого паттерна вместе с доработанной системой реактивности позволяет разработчикам отказаться от использования VueX, MobX или любой другой библиотеки для управления стейтом приложения, и получить больший контроль над архитектурой приложения. Если все же решите отказаться от стейт менеджмента с помощью библиотек, то следует помнить, что

С большой силой приходит большая ответственность.

Появление provide и inject во Vue

Для инъекции зависимостей в компонентах Vue можно было использовать свойства provide и inject начиная ещё с версии 2.2.0. Но тогда они были частью объявления компонентов, и принцип их работы был интуитивно понятен: когда Vue находит в компоненте provide, то он ищет среди предков компонента нужные значение заданные свойством inject.

Как работает Vue Dependency Injection
Как работает Vue Dependency Injection

Переход на функциональную основу в третьей версии

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

// … const injectFunction = () => inject('key2', 'value2');  export default defineComponent({   setup() {     inject('key', 'value'); // работает     injectFunction(); // тоже работает     setTimeout(injectFunction); // а это не работает   },   methods: {     // вызов метода при браузерном событии (e.g. click) не сработает, а в методах жизненного цикла будет работать      injectFromMethod() {       inject('key', 'value');     },   }, }); 

Как устроены inject и provide под капотом

Внутрненний принцип работы этих функций не является очевидным. По крайней мере, мне он не показался очевидным, поэтому я копался в исходниках, чтоб во всем разобраться. Можно было б предположить, что функции provide и inject могут как-то знать в каком контексте вызваны, но это невозможно. Единственный способ передавать контекст, в котором вызвана функция в js это ее аргументы или this, но мы не используем ни то, ни другое. На самом деле контекст и не передается. Нам и не нужно его передавать. Vue просто хранит данные компонента, обработка которого происходит в данный момент, в отдельной переменной уровня видимости модуля. Просто представьте следующий код:

// … let currentInstance = null function renderComponent(componentInstance) {   currentInstance = componentInstance;   componentInstance.setup();   // do rest rendering logic   // also set currentInstance to be componentInstance whenever lifecycle hook is called }  export function provide(key, value) {   if (!currentInstance.provides) { 	  currentInstance.provides = Object.create(currentInstance.parent.provides);   }   currentInstance.provides[key] = value; }  export function inject(key) {   return currentInstance.parent.provides[key]; }

Выше наведен очень утрированный пример, без обработки многих крайних случаев (нулевые значения, асинхронный setup, дебаг логи, и т.д.), но в целом он дает нам возможность понять, как Vue DI работает на самом деле. Для хранения информации о том какие данные обеспечены (provided) используется обычное прототипное наследование, а для того, чтоб инъецировать зависимость мы просто получаем её из свойства provides компонента, обработка которого происходит в данный момент.

Мне подход с изменением значения переменной компонента, который обрабатывается в данный момент, показался хрупким и не идиоматичным, но тем не менее на практике он показывает себя достаточно надежным. Все приложения, в которых я использовал provide и inject, работали стабильно и проблем не возникало.

Много ключевого функционала Vue опирается на возможность получить компонент, который обрабатывается в данный момент. Более того Vue предоставляет возможность  разработчикам самим получить этот компонент с помощью экспортированной функции getCurrentInstance.

Как это можно использовать?

Зависимости, предоставленные с помощью provide можно получить только в дочерних компонентах, но не в компоненте который их предоставляет.

// … // такой код выведет предупреждения, а значение переменной value будет равно undefined export default defineComponent({   setup() {     provide('key', 'value');     const value = inject('key');   }, }); 

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

// … // Такой код будет работать как нам нужно, т.е. значение переменной value будет получено // из свойства provide данного компонента export const injectFromCurrent = (key: string) => {   const componentInstance = getCurrentInstance() as any;   const componentProvides = componentInstance?.provides || {};   if (!(key in componentProvides)) {     console.warn(`injection ${key} not found.`);     return undefined;   }    const dependency = componentProvides[key];   return dependency; }; // … export default defineComponent({   setup() {     provide('key', 'value');     const value = injectFromCurrent('key');   }, }); 

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

Выводы

Vue предоставляет разработчикам обширный функционал для эффективной разработки приложений и уже обошёл React по количеству звёзд на Github, что свидетельствует, о его популярности среди разработчиков. Использование 3 версии ещё не так распространено, хотя последняя версия фреймворка предоставляет много интересного функционала. Знание об их внутреннем устройстве может быть полезно как при решении задач связанных непосредственно связанных с Vue, так и при похожих фич у себя в приложении, не использующем какой-либо фреймворк.

ссылка на оригинал статьи https://habr.com/ru/post/564716/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *