Provide/inject.
Часть 1: Основы использования
Сегодня затронем тему с которой чаще всего ко мне подходили люди с удивлением: как так, ты призываешь использовать
Но для начала я должен успокоить тех, кто пришел из Vue2 и все еще боится использовать столь мощный механизм во Vue3:
1. Прекрасно типизируется
2. Дружит с реактивностью замечательно (хотя конечно есть способы сломать ее)
3. Имеет в подспорье композаблы, а не миксины для удобной работы с ними
4. За счет изоляции внутри компонента менее магический (речь о том что ты не можешь инжектить себе в этот же компонент что ты запровайдил)
5. Вместо строк нам доступны ключи-символы, что снимает вероятность получения коллизий
Отлично, теперь когда мы разобрались с тем почему миксины во Vue2 и Vue3 совершенно на разных уровнях. я даю список рекомендаций по использованию:
1. Использовать TypeScript (да-да это тот самый способ добиться корректной типизации provide/inject)
2. Всегда использовать ключи символы
- так как это убережет вас от возможных коллизий
- Повысит изоляцию, так как без доступа к символу никто не сможет считать значение
- Дает возможность использовать типизацию в полной мере
3. Всегда использовать ТОЛЬКО внутри композаблов
- прячем такие кишки под капот
- ограничиваем API для взаимодействия с нашим контекстом, теперь в композабле вы определяете как можно пользоваться вашим
- это уберет необходимость экспортировать символ (держите его всегда "в секрете")
- вы сможете в нем обработать любые ситуации (например как поступить если такого ключа нет?)
4. Делайте раздельные композаблы по мере необходимости. Например разделение на того кто может провайдить данные и композабл для тех кто его инжектит
- вновь позволит сохранить вам удобное API
- это безопасно и не вызовет каши в вызовах
На самом деле тема крайне обширная и в 1 пост ее не уместить, но эти несложные 4 правила уже превратят страшный
Следующие темы:
- Где и когда использовать?
- Продвинутое использование
- Полноценное DI на provide/inject
PS. попробую ввести теги для вашего удобства
#di #my #learn
Часть 1: Основы использования
Сегодня затронем тему с которой чаще всего ко мне подходили люди с удивлением: как так, ты призываешь использовать
provide/inject
это же кошмар. Далее следует обсуждение в котором выясняется, что у человека табу на provide/inject появилось в момент еще изучения Vue2 или изучения Vue по материалам актуальным для Vue2. Также я не совсем согласен с некоторыми рекомендациями в документации, они хороши, но только когда мы используем provide/inject
сами по себе, вне контекста моих советов.Но для начала я должен успокоить тех, кто пришел из Vue2 и все еще боится использовать столь мощный механизм во Vue3:
1. Прекрасно типизируется
2. Дружит с реактивностью замечательно (хотя конечно есть способы сломать ее)
3. Имеет в подспорье композаблы, а не миксины для удобной работы с ними
4. За счет изоляции внутри компонента менее магический (речь о том что ты не можешь инжектить себе в этот же компонент что ты запровайдил)
5. Вместо строк нам доступны ключи-символы, что снимает вероятность получения коллизий
Отлично, теперь когда мы разобрались с тем почему миксины во Vue2 и Vue3 совершенно на разных уровнях. я даю список рекомендаций по использованию:
1. Использовать TypeScript (да-да это тот самый способ добиться корректной типизации provide/inject)
2. Всегда использовать ключи символы
- так как это убережет вас от возможных коллизий
- Повысит изоляцию, так как без доступа к символу никто не сможет считать значение
- Дает возможность использовать типизацию в полной мере
3. Всегда использовать ТОЛЬКО внутри композаблов
- прячем такие кишки под капот
- ограничиваем API для взаимодействия с нашим контекстом, теперь в композабле вы определяете как можно пользоваться вашим
provide/inject
, а не "бросаем что-то куда-то пусть используют"- это уберет необходимость экспортировать символ (держите его всегда "в секрете")
- вы сможете в нем обработать любые ситуации (например как поступить если такого ключа нет?)
4. Делайте раздельные композаблы по мере необходимости. Например разделение на того кто может провайдить данные и композабл для тех кто его инжектит
- вновь позволит сохранить вам удобное API
- это безопасно и не вызовет каши в вызовах
На самом деле тема крайне обширная и в 1 пост ее не уместить, но эти несложные 4 правила уже превратят страшный
provide/inject
в одного из лучших друзей, сохранят предсказуемость и прозрачность в вашем приложении.Следующие темы:
- Где и когда использовать?
- Продвинутое использование
- Полноценное DI на provide/inject
PS. попробую ввести теги для вашего удобства
#di #my #learn
vuejs.org
Vue.js
Vue.js - The Progressive JavaScript Framework
👍32❤7🔥6🍌1👀1
Provide/inject.
Часть 2: Где и когда использовать?
В прошлой части мы рассмотрели рекомендации по использованию provide/inject (давайте для краткости будем называть это контекстом, так как это наименование используется в большинстве фреймворков). И так, где же будет уместно использование контекста.
Для начала я бы рассмотрел природу этого механизма, чтобы делать какие-то выводы:
1. Оно сквозное - самый принцип который узнают все пользователи, что это способ избежать необходимости прокидывать пропсы, чтобы доносить его до низлежащих компонентов, что роднит его с глобальным стейтом, который тоже доступен ото всюду
2. Оно иерархическое - а вот это наиболее интересный и важный пункт. Это свойство позволяет от любого компонента строить иерархию контекстов на всю глубину/до переопределения. Это роднит его с пропсами, когда каждый компонент имеет свой стейт, только тут поддерево
Таким образом оно позволяет нам что-то синхронизировать на всей протяженности поддерева на котором оно определено. Где бывает такое нужно:
1. Компоненты с четкой иерархией. Например
2. Работа плагинов и прочие глобальные "неявное API". Вот вы используете Pinia / Vuex / VueRouter и тд. А как вообще они узнают информацию о роутере и так далее, они на самом деле они прокидывают контекст в корень приложения и далее вам не нужно переживать как они это используют, они просто достанут из контекста всю информацию для работы API. Поэтому если вам нужно универсальное API, без глобального стора(например потому что вы пишите внутренний плагин или возможно множество инстансов), то это отличный вариант.
3. Сервисы ограниченные поддеревом. Наиболее интересная и при этом наиболее неразвитое направление в использовании. Это буквально наиболее активно используемый механизм в ангуляре, на его основе работает продвинутое DI и так далее. Что это такое? Вот обычно мы логику с данными которая может использоваться несколькими компонентами в глобальный стор, а что делать если это нужно нам лишь в определенной части приложения или нам вообще нужно множество таких инстансов запустить? Тут идут как раз трюки с добавлением
4. API важное лишь на определенных страницах. Это по факту 3ий пункт, но более специализированный. По сути это призыв не пихать все подряд в глобальный стор, на то он и глобальный, что должен иметь смысл ВЕЗДЕ. А если он несет смысл лишь на конкретных страницах, то это API этих страниц и лучшим решением будет запуска сервиса на этих страницах и прокидыванием его в контекст и взаимодействиями через него же. Таким образом можно сохранять ваш стм чистым от специфичной логики и держать специфичные сервисы ближе к месту их использования (особенно если вы используете модульную архитектуру проекта)
Таким образом мы обрисовали основные причины для использования provide/inject во Vue приложениях
#di #my #learn
Часть 2: Где и когда использовать?
В прошлой части мы рассмотрели рекомендации по использованию provide/inject (давайте для краткости будем называть это контекстом, так как это наименование используется в большинстве фреймворков). И так, где же будет уместно использование контекста.
Для начала я бы рассмотрел природу этого механизма, чтобы делать какие-то выводы:
1. Оно сквозное - самый принцип который узнают все пользователи, что это способ избежать необходимости прокидывать пропсы, чтобы доносить его до низлежащих компонентов, что роднит его с глобальным стейтом, который тоже доступен ото всюду
2. Оно иерархическое - а вот это наиболее интересный и важный пункт. Это свойство позволяет от любого компонента строить иерархию контекстов на всю глубину/до переопределения. Это роднит его с пропсами, когда каждый компонент имеет свой стейт, только тут поддерево
Таким образом оно позволяет нам что-то синхронизировать на всей протяженности поддерева на котором оно определено. Где бывает такое нужно:
1. Компоненты с четкой иерархией. Например
Tabs
и Tab
. В этом случае очевидно, что Tab
без Tabs
не имеет смысла и одно вложено в другое, а Tabs
может отправлять необходимые данные или использование данные от Tab
через контекст. Еще примеры таких компонентов: RadioGroup
+Radio
/ Form
+inputs
. На самом деле современные headless ui библиотеки буквально полностью построены на таких иерархиях и иногда эти особенности называют "анатомией компонента", те какие компоненты во что могут быть вложены и из чего состоят. Поэтому если вам нужно создать компонент который работает в паре с другим компонентом, то это нормально завести для их взаимодействия контекст.2. Работа плагинов и прочие глобальные "неявное API". Вот вы используете Pinia / Vuex / VueRouter и тд. А как вообще они узнают информацию о роутере и так далее, они на самом деле они прокидывают контекст в корень приложения и далее вам не нужно переживать как они это используют, они просто достанут из контекста всю информацию для работы API. Поэтому если вам нужно универсальное API, без глобального стора(например потому что вы пишите внутренний плагин или возможно множество инстансов), то это отличный вариант.
3. Сервисы ограниченные поддеревом. Наиболее интересная и при этом наиболее неразвитое направление в использовании. Это буквально наиболее активно используемый механизм в ангуляре, на его основе работает продвинутое DI и так далее. Что это такое? Вот обычно мы логику с данными которая может использоваться несколькими компонентами в глобальный стор, а что делать если это нужно нам лишь в определенной части приложения или нам вообще нужно множество таких инстансов запустить? Тут идут как раз трюки с добавлением
id
к именам сторов в Pinia и прочие трюки по "размножению" STM, а по факту раз это имеет смысл лишь в определенном поддереве приложения, то именно этот механизм наиболее аккуратно может быть взят для использования. Таким образом можно даже включать различные цветовые темы в разных подчастях приложения. 4. API важное лишь на определенных страницах. Это по факту 3ий пункт, но более специализированный. По сути это призыв не пихать все подряд в глобальный стор, на то он и глобальный, что должен иметь смысл ВЕЗДЕ. А если он несет смысл лишь на конкретных страницах, то это API этих страниц и лучшим решением будет запуска сервиса на этих страницах и прокидыванием его в контекст и взаимодействиями через него же. Таким образом можно сохранять ваш стм чистым от специфичной логики и держать специфичные сервисы ближе к месту их использования (особенно если вы используете модульную архитектуру проекта)
Таким образом мы обрисовали основные причины для использования provide/inject во Vue приложениях
#di #my #learn
🔥27🍌2
Provide/inject.
Часть 2.1: Когда НЕ НАДО использовать?
Мы разобрались с основными мотивациями для использования. Теперь разберемся, когда использование
1. Пункт специально предназначенный для реактеров. НЕ НАДО использовать контексты как глобальное хранилище и пихать в них все, что нужно глобально. Для этого есть множество других способов (да даже переменные на уровне модулей). Конечно, у вас будет работать все как и задумано, но вам не захочется использовать это таким способом (если вы прочли 1 часть), так как это будет крайне избыточно, да и профитов вы от этого не получите.
2. Контексты это не замена пропсам/ивентам, это принципиально другой механизм, это скорее способ установления множественных связей те когда МНОЖЕСТВУ компонентов в поддереве нужна связь с ОДНИМ компонентом родителя, а не 1 к 1(в случае плагинов можно считать что это связь ПРИЛОЖЕНИЯ и компонентов использующих API плагина). И у данных соединенных контекстом должна быть четкая цель для этого, а не просто экономия на создании пропсов. Стоит задуматься о "уходе от пропс дриллинга", если вам приходится заводить пропсы которые не имеют никакого смысла для промежуточных компонентов, кроме как быть прокинутыми еще дальше, в этом случае подумать о переходе на контексты можно, так как это значит, что вам необходима именно сквозная связь компонентов.
3. Не бросайте в контексты все подряд. Очевидное, но важное пояснение. Старайтесь удерживать количество контекстов минимально необходимым. Так как мутации в контекстах менее явные и не логгируются девтулзами, то лучше этот объем снижать, чтобы не превратить вашу кодовую базу в клубок из мутаций. Опять же, вам тут поможет четкое разделение по композаблам и использование контекстов только через них. Те если у вас есть ПРОСТАЯ возможность не использовать
Я надеюсь, что мне удалось донести, когда стоит использовать
#di #my #learn
Часть 2.1: Когда НЕ НАДО использовать?
Мы разобрались с основными мотивациями для использования. Теперь разберемся, когда использование
provide/inject
либо не имеет смысла, либо будет вредоносно.1. Пункт специально предназначенный для реактеров. НЕ НАДО использовать контексты как глобальное хранилище и пихать в них все, что нужно глобально. Для этого есть множество других способов (да даже переменные на уровне модулей). Конечно, у вас будет работать все как и задумано, но вам не захочется использовать это таким способом (если вы прочли 1 часть), так как это будет крайне избыточно, да и профитов вы от этого не получите.
2. Контексты это не замена пропсам/ивентам, это принципиально другой механизм, это скорее способ установления множественных связей те когда МНОЖЕСТВУ компонентов в поддереве нужна связь с ОДНИМ компонентом родителя, а не 1 к 1(в случае плагинов можно считать что это связь ПРИЛОЖЕНИЯ и компонентов использующих API плагина). И у данных соединенных контекстом должна быть четкая цель для этого, а не просто экономия на создании пропсов. Стоит задуматься о "уходе от пропс дриллинга", если вам приходится заводить пропсы которые не имеют никакого смысла для промежуточных компонентов, кроме как быть прокинутыми еще дальше, в этом случае подумать о переходе на контексты можно, так как это значит, что вам необходима именно сквозная связь компонентов.
3. Не бросайте в контексты все подряд. Очевидное, но важное пояснение. Старайтесь удерживать количество контекстов минимально необходимым. Так как мутации в контекстах менее явные и не логгируются девтулзами, то лучше этот объем снижать, чтобы не превратить вашу кодовую базу в клубок из мутаций. Опять же, вам тут поможет четкое разделение по композаблам и использование контекстов только через них. Те если у вас есть ПРОСТАЯ возможность не использовать
provide/inject
НЕ ИСПОЛЬЗУЙТЕ ИХ. Но и не демонизируйте избегая контексты любыми путями. Например модульная переменная с точки зрения прозрачности использования не особо лучше помогает понять кто ее мутирует, а вот пропсы/ивенты делают это более явным. Но и 3 уровня бессмысленного прокидывания пропсов до целевого компонента сильно лучше ситуацию не сделают. Однако тут у меня нет для вас однозначного рецепта, сколько уровней и когда дают смысл к использованию, все-таки построение архитектуры во многом основывается на опыте и вкусе разработчика, а не четкая блок схема когда и что использоватьЯ надеюсь, что мне удалось донести, когда стоит использовать
provide/inject
. Если же у вас все еще остались вопросы, то буду рад на них ответить.#di #my #learn
🔥20❤1💯1
Provide/inject
Часть 3: Используем API на практике
Во Vue у нас есть Suspense, но он уже кучу лет висит в Experimental, да и не нужен он особо сейчас во Vue в текущем виде (данное обсуждение я бы вынес в отдельный пост). Однако, что нам делать если мы хотим определять лоадеры в родителе, а дети подгружали бы данные и не парились насчет кто и как показывает лоадер и это вместо того, чтобы делать парады спиннеров (когда каждый компонент имеет свой лоадер) или 1 глобальный лоадер перекрывающий все приложение. Вот с таким запросом заходили в чате.
И решение достаточно простое:
1. Пусть компонент который может показывать лоадер запровайдит API ниже через
2. Дочерний компонент через API сможет его подгрузить через
3. Соответственно родитель может знать о всех загрузках и показывать Loader когда необходимо
Однако у этого решения есть 2 специфичных минуса
- При первом рендере родителя он еще не знает будут ли грузиться дети (можем первый рендер вообще ничего не рендерить как пример)
- Мы не можем показывать loader / дети по условному v-if и нам нужно думать, как их отображать (например через v-show)
И вот получается базовая реализация. Изучите ее пожалуйста, так как в ней использованы все советы из прошлых постов.
Однако, как поведет такая реализация себя если компонент с лоадером мы вложим в компонент с лоадером? Сейчас каждый компонент с лоадером порождает независимый лоадер: то есть лоадер выше не знает о загрузках которые идут от лоадера ниже. И иногда данное поведение нас устраивает, а что если мы хотим, чтобы наш компонент показывал Loader даже, когда грузятся внутренние лоадеры? В этом случае мы могли бы просто не использовать
Теперь мы имеем достаточное простое и лаконичное API для работы с лоадерами на проекте при этом обладая полной гибкостью в работе с ними. И такой подход было бы крайне затруднительно реализовать без механизма provide/inject
#di #my #learn #example
Часть 3: Используем API на практике
Во Vue у нас есть Suspense, но он уже кучу лет висит в Experimental, да и не нужен он особо сейчас во Vue в текущем виде (данное обсуждение я бы вынес в отдельный пост). Однако, что нам делать если мы хотим определять лоадеры в родителе, а дети подгружали бы данные и не парились насчет кто и как показывает лоадер и это вместо того, чтобы делать парады спиннеров (когда каждый компонент имеет свой лоадер) или 1 глобальный лоадер перекрывающий все приложение. Вот с таким запросом заходили в чате.
И решение достаточно простое:
1. Пусть компонент который может показывать лоадер запровайдит API ниже через
useLoaderProvider
2. Дочерний компонент через API сможет его подгрузить через
useLoader
и достать оттуда функцию wait
, которая принимает Promise и сообщает родителю, что мы грузим какие-то дочерние данные3. Соответственно родитель может знать о всех загрузках и показывать Loader когда необходимо
Однако у этого решения есть 2 специфичных минуса
- При первом рендере родителя он еще не знает будут ли грузиться дети (можем первый рендер вообще ничего не рендерить как пример)
- Мы не можем показывать loader / дети по условному v-if и нам нужно думать, как их отображать (например через v-show)
И вот получается базовая реализация. Изучите ее пожалуйста, так как в ней использованы все советы из прошлых постов.
Однако, как поведет такая реализация себя если компонент с лоадером мы вложим в компонент с лоадером? Сейчас каждый компонент с лоадером порождает независимый лоадер: то есть лоадер выше не знает о загрузках которые идут от лоадера ниже. И иногда данное поведение нас устраивает, а что если мы хотим, чтобы наш компонент показывал Loader даже, когда грузятся внутренние лоадеры? В этом случае мы могли бы просто не использовать
provide
во внутреннем лоадере, если он увидит уже запровайженное значение. Таким образом получаем улучшенный пример.Теперь мы имеем достаточное простое и лаконичное API для работы с лоадерами на проекте при этом обладая полной гибкостью в работе с ними. И такой подход было бы крайне затруднительно реализовать без механизма provide/inject
#di #my #learn #example
🔥21👍6🤨2
@click="router.push('/hell')"
Так, потихоньку возвращаемся к регулярным постам. И сегодня у меня небольшая напоминалка: если это возможно, то всегда предпочитайте нормальную ссылку вместо
router.push
.Ведь одно из самых бесящих вещей на сайтах, когда ты хочешь что-то открыть в новой вкладке, а тебе не дают и нет никаких причин по которой это было сделано. А уж о доступности в таком подходе и говорить не приходится.
еще раз. если видите что-то подобное
@click="router.push('/smth')"
То приложите усилия, чтобы это стало
<RouterLink>
-ом или на крайний случай <a>
, таким образом вы сделаете мир чуточку но лучше (и никто не захочет вам пожелать отдельного котла)#remainder #learn #ux
👍47💯13🥰3❤2🫡1
Невнимательность и реактивность
Недавно в чате человек столкнулся с проблемой, что "меняя нереактивный массив он видел изменения в рендере". В чате сразу начали возникать: мол раз reactive был передан, то это уже реактивный массив. Давайте разбираться
1. Меняется ли исходный объект при передачи в reactive?
Нет. Объект сам не меняется, однако Proxy создаваемое от reactive будет мутировать именно переданный объект. Возможно это очевидно, но:
- во Vue2 редактировался исходный объект. Там каждое поле объекта становилось getter + setter (при этом во всю глубину) и конечно же такой подход имел свои последствия.
- В других фреймворках, например Svelte может быть иное поведение. Там Proxy лишь основывается на начальном объекте и мутации не идут в исходный объект
2. Отслеживает ли Vue мутации в исходном нереактивном объекте?
Нет. Однако, Proxy все-таки основан на оригинальном объекте и если кто-то изменит объект, а потом это значение прочтут реактивно, то, конечно же, будет получено актуальное значение
Кажется очевидным, но в Svelte опять же после создания реактивного значения связь с нереактивным теряется
Вот по сути 2 ключевых момента которые ввели в ступор разработчика, он не заметил, что каждую секунду из-за
Однако затронем пару еще интересных моментов:
Знает ли Vue, что уже существует Proxy для отслеживания конкретного объекта?
Да. Vue имеет специальные
Проверить это достаточно легко:
Это важная часть оптимизация реактивности Vue позволяющая существенно экономить память при работе с объектами, так как на каждый объект не более 1 соответствующего Proxy. Однако у вас нет к этому доступа
Есть ли какие-то данные в Proxy от reactive которых нет в исходном объекте?
Есть, их не существует в объекте, но Proxy особым образом проверяет на доступ к ним. И это
#learn #reactivity
Недавно в чате человек столкнулся с проблемой, что "меняя нереактивный массив он видел изменения в рендере". В чате сразу начали возникать: мол раз reactive был передан, то это уже реактивный массив. Давайте разбираться
1. Меняется ли исходный объект при передачи в reactive?
Нет. Объект сам не меняется, однако Proxy создаваемое от reactive будет мутировать именно переданный объект. Возможно это очевидно, но:
- во Vue2 редактировался исходный объект. Там каждое поле объекта становилось getter + setter (при этом во всю глубину) и конечно же такой подход имел свои последствия.
- В других фреймворках, например Svelte может быть иное поведение. Там Proxy лишь основывается на начальном объекте и мутации не идут в исходный объект
2. Отслеживает ли Vue мутации в исходном нереактивном объекте?
Нет. Однако, Proxy все-таки основан на оригинальном объекте и если кто-то изменит объект, а потом это значение прочтут реактивно, то, конечно же, будет получено актуальное значение
const a = { hello: 0 }
const aReactive = reactve(a)
const b = ref(2)
const mult = computed(() => aReactive.hello * b.value)
console.log(mult.value) // 0 * 2 = 0
a.hello = 2
console.log(mult.value) // все еще 0, Vue не знает об обновлении
b.value = 1
console.log(mult.value) // 2 * 1 = 2, так как изменилось другое реактивное значение, то Vue пересчитал computed
Кажется очевидным, но в Svelte опять же после создания реактивного значения связь с нереактивным теряется
Вот по сути 2 ключевых момента которые ввели в ступор разработчика, он не заметил, что каждую секунду из-за
useFps
у него происходил ререндр компонента и не мог понять почему видит обновления в нереактивных данных.Однако затронем пару еще интересных моментов:
Знает ли Vue, что уже существует Proxy для отслеживания конкретного объекта?
Да. Vue имеет специальные
WeakMap
-ы со всеми существующими Proxy от реактивности (но доступа к ним прямого нет, он спрятан в исходниках @vue/reactivity)Проверить это достаточно легко:
const a = { hello: 0 }
const aReactive1 = reactive(a)
const aReactive2 = reactive(a)
console.log(aReactive1 === aReactive2) // true
Это важная часть оптимизация реактивности Vue позволяющая существенно экономить память при работе с объектами, так как на каждый объект не более 1 соответствующего Proxy. Однако у вас нет к этому доступа
Есть ли какие-то данные в Proxy от reactive которых нет в исходном объекте?
Есть, их не существует в объекте, но Proxy особым образом проверяет на доступ к ним. И это
ReactiveFlags
и хотя у вас есть доступ к этим флагам, все-таки лучше использовать соответсвющие утилити функции от Vueconsole.log(aReactive[ReactiveFlags.IS_REACTIVE] === isReactive(aReactive))
// и тд
#learn #reactivity
🔥27👍8❤4