Vue3 provide-inject di

от автора

Vue3 прекрасный фреймворк, писать на котором одно удовольствие. Кто не пробовал — попробуйте, а кто юзает — наслаждайтесь. Сегодня хотелось бы написать о такой возможности, как передача данных в дочерние компоненты, с помощью provide-inject механизма. Конечно, всё познаётся в сравнении, поэтому затронем так же props.

И то, и другое — способ передачи данных вниз по дереву компонентов.

Почти в 100% приложений встречаются подобные кейсы, и не единожды

Почти в 100% приложений встречаются подобные кейсы, и не единожды

Передать данные из родителя в «потомок дочернего компонента» можно несколькими способами:
1. С помощью props.
Во входящий props дочернего компонента помещается объект, который мы хотим передать из родителя.

ParentComponent.vue  <template>   <child-component :data="someData" v-for="someData in allData" :key="someData.id"/> </template>
ChildComponent.vue  <script setup lang="ts">      interface DataTypes {...}      const props = defineProps<{     data: DataTypes   }>()  </script> <template>   <div>{{ props.data }}</div> </template>

Простой и понятный код — в ходе итерации по массиву данных в компоненте ParentComponent.vue мы создаём однотипные элементы шаблона html (<child-component>) и передаём в каждый шаблон свой элемент для отрисовки. Соответственно в каждом child-component мы этот элемент принимаем как props и отрисовываем в html.

Но что, если у ChildComponent есть свой потомок?

ChildComponent.vue  <script setup lang="ts">      interface DataTypes {...}      const props = defineProps<{     data: DataTypes   }>()  </script> <template>   <div>{{ props.data }}</div>   <child-of-child-component :item="props.data"> </template>

В 14 строке я добавил дочерний компонент для нашего ChildComponent.vue и передал ему в качестве props объект, так же полученный пропсом из ParentComponent.vue.
Получилась своеобразная лесенка из данных.

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

Но… что, если ступеней будет больше? Насколько понятным станет код? А безопасность? Допустим, мы захотим изменить компонент, который в середине лестницы. Тогда все дочерние компоненты могут просто сломаться. Нам придётся учитывать при перепроектировании компонента эти условия, что само по себе уже есть ограничение и нарушение принципа одной ответственности — наш компонент не просто инкапсулирует какую то логику внутри себя, как в коробочке, но еще и передаёт данные в следующий компонент, не являясь исходным «отправителем» этой информации.

Допустим, в конечном компоненте нам нужно каким то образом повлиять на props ? Мы сделаем emit события вверх по цепочке компонента, перехватим это событие в родителе.. и повторим этот способ столько раз, сколько ступеней в нашей лестнице.

Развивать тему emit я не буду, но для полноты картины я посчитал нужным сделать эти пометки.

Итак, проблемы такой архитектуры.

  • Нарушение принципа одной ответственности. Я считаю это главным минусом. При перепроектировании или изменении компонента мы вынуждены учитывать дополнительные факторы, могущие повлиять на другие компоненты

  • Читабельность, безопасность и очевидность данных. Чтобы отследить источник данных, придётся несколько раз перемещаться по компонентам, параллельно отслеживая возможные emit на изменение этих данных.

Давайте рассмотрим частичную альтернативу этому:

2. С помощью provide-inject

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

Передаём данные из родителя:

ParentComponent.vue  <script setup lang="ts">   provide(key,someData) </script>

И принимаем в том потомке, где они необходимы, минуя промежуточные:

ChildOfChildComponent.vue  <script setup lang="ts">   const item = inject(key) </script>

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

key — идентификатор передаваемых данных. В документации рекомендуется использовать для обозначения ключей тип Symbol. И я с этим полностью согласен — этот тип данных генерирует полностью уникальное значение, соответственно проблем со случайным «совпадением» ключей не будет.

От себя добавлю — называйте ключи так, чтобы было сразу понятно откуда приходят данные! Так намного читабельнее и очевиднее!

Давайте же вынесем ключ в файл injectKey.js

 injectKey.js  export const DATA_FROM_ParentComponent_VUE = Symbol()

Соответственно перепишем наш provide-inject:

ParentComponent.vue  <script setup lang="ts">   provide(DATA_FROM_ParentComponent_VUE, data) </script>
ChildOfChildComponent.vue  <script setup lang="ts">   const item = inject(DATA_FROM_ParentComponent_VUE) </script>

Отлично, теперь данные можно отобразить в шаблоне!

Но, что если мы захотим изменить полученные путём инжектирования данные? Как нам быть? Неужели опять строить лесенку из emit ?

Спешу вас обрадовать — есть гораздо более интересный способ это сделать! Секрет в том, что можно запровайдить реактивную переменную!

Так как данные, полученные путём inject не являются по факту пропсами, мы можем их менять прямо в том компоненте, где мы их получили. Дополню наш код:

ParentComponent.vue  <script setup lang="ts">   const data: Ref<number> = ref(2)   provide(DATA_FROM_ParentComponent_VUE, data) </script>
ChildOfChildComponent.vue  <script setup lang="ts">   const data: Ref<number>|undefined = inject(DATA_FROM_ParentComponent_VUE)   function doubleData(){     if(data){       data.value *= 2     }   } </script> <template>     <div @click="doubleItem">{{ data }}</div> </template>

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

Но что, если мы хотим ЯВНО в родителе указать функцию, которая должна выполняться с переданными данными? Хорошо, в этом случае можно в родителе объявить дополнительный provide !

ParentComponent.vue  <script setup lang="ts">   const data: Ref<number> = ref(2)   provide(DATA_FROM_ParentComponent_VUE, data)   provide(CHANGE_DATA_FROM_ParentComponent_VUE, () => {     data.value *= 2 }) </script>

В этом случае, в ЛЮБОМ дочернем компоненте, можно заинжектить функцию:

ChildOfChildComponent.vue / ChildComponent.vue  <script setup lang="ts">   const double: (() => number)|undefined = inject(CHANGE_DATA_FROM_ParentComponent_VUE) </script> <template>     <div @click="double">...</div> </template>

Никаких emit, прямое изменение значения переменной прямо в дочернем компоненте

Подытожу: provide-inject является достаточно продуманным и хорошим вариантом обработки и передачи информации по дереву дочерних компонентов, при условии, когда альтернативные варианты невозможны, либо крайне затруднительны. Однако я бы не советовал увлекаться этим механизмом, и заменять им все подрят пропсы или эмиты. Этот механизм хорош в единичных случаях, когда НЕТ АЛЬТЕРНАТИВЫ. Просто представьте файл с ключами длинной в страницу, и вы поймёте о чем я.

Думаю, это всё что я хотел рассказать о provide-inject. Спасибо за внимание.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Часто пользуетесь provide-inject?

4.6% Да, часто4
6.9% Иногда, но чаще чем редко)6
35.63% Редко, но чаще пары раз31
52.87% Вообще не пользовался46

Проголосовали 87 пользователей. Воздержались 7 пользователей.

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


Комментарии

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

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