5 прийомів на допомогу розробці на vuex
Нещодавно вирішив розібратися з vue.js. Найкращий спосіб вивчити технологію — щось на ній написати. З цією метою було переписано мого старого планувальника маршрутів, і вийшовось такий проект. Код вийшов досить великим для того, щоб зіткнутися із завданням масштабування.
У цій статті наведу низку прийомів, які, як на мене, допоможуть у розробці будь-якого великого проекту. Цей матеріал для вас, якщо ви вже написали свій todo-лист на vue.js+vuex, але ще не закопалися у велике велосипедобудування.

1. Централізована шина подій (Event Bus)
Будь-який проект на vue.js складається із вкладених компонентів. Основний принцип – props down, events up. Підкомпонент отримує від батька дані, які він не може змінювати, та перелік подій батька, які він може запустити.
Принцип придатний, але створює сильний зв'язок. Якщо цільовий компонент глибоко вкладений, доводиться протягувати дані та події через усі обгортки.
Розберемося з подіями. Найчастіше корисно мати глобальний event emitter, з яким може спілкуватися будь-який компонент незалежно від ієрархії. Його дуже легко зробити, додаткові бібліотеки не потрібні:
Після цього в будь-якому компоненті з'являється доступ до this.$bus, можна підписуватись на події через this.$bus.$on() і викликати їх через this.$bus.$emit(). Ось приклад.
Дуже важливо розуміти, що this.$bus — глобальний об'єкт на всю програму. Якщо забувати відписуватись, компоненти залишаються в пам'яті цього об'єкта. Тому на кожен this.$bus.$on у mounted повинен бути відповідний this.$bus.$off у beforeDestroy. Наприклад, так:
2. Централізована шина промісів (Promises Bus)
Іноді у компоненті потрібноініціалізувати асинхронну штуку (наприклад, інстанцій google maps), до якої хочеться звертатися з інших компонентів. Для цього можна організувати об'єкт, який зберігатиме проміси. Наприклад, такий. Як і у випадку в event bus, не забуваємо видалятися при деініціалізації компонента. І взагалі, вказаним вище способом до vue можна причепити будь-який зовнішній об'єкт із будь-якою логікою.
3. Плоскі структури (flatten store)
У складному проекті дані дуже часто вкладені. Працювати з такими даними незручно як у vuex, так і redux. Рекомендується зменшувати вкладеність, наприклад, скориставшись утилітою normalizr. Утиліта це добре, але ще краще розуміти, що вона робить. Я не відразу прийшов до розуміння плоскої структури, для таких самих типів себе розгляну докладний приклад.
Маємо проекти, у кожному масив шарів, у кожному шарі масив сторінок: projects > layers > pages. Як організувати сховище?
Перше, що спадає на думку — звичайна вкладена структура:
Можна вказувати повний шлях, типу
Але це означає, що кожен маленький компонент рендерингу сторінки потрібно протягувати всю ієрархію, і projectId, і layerId. Незручно.
Друга спроба, з SQL:
Тепер дані легко змінювати. Але важко бігати. Щоб вивести всі сторінки в одному шарі, потрібно пробігти взагалі всіма сторінками. Це може бути заховано в getter-і, або в рендеринг шаблону, але пробіжка все одно буде.
Третя спроба підходу normalizr:
Тепер всі сторінки шару можуть бути отримані через тривіальний гетер
Зауважимо, що геттер не бігає за списком усіх сторінок. Дані легко змінювати. Порядок сторінок у шарі заданий в об'єкті layer, і це теж правильно, оскільки процедура пересортування зазвичай знаходиться вкомпонент, який виводить список об'єктів, у нашому випадку це компонент, який рендерит layer.
4. Мутації не потрібні
Відповідно до правил vuex, зміни даних сховища повинні відбуватися тільки у функціях-мутаціях, мутації мають бути синхронними. У vuex знаходиться основна логіка програми. Тому блок валідації даних також буде логічним включити до сховища.
Але валідація далеко не завжди є синхронною. Отже, по крайнього заходу частина валідаційної логіки перебуває над мутаціях, а діях (actions).
Пропоную не розбивати логіку і зберігати в actions взагалі всю валідацію. Мутації стають примітивними, складаються із елементарних присвоювань. Але тоді до них не можна звертатися безпосередньо із додатка. Тобто. мутації - якась утилітарна штука всередині сховища, яка корисна хіба що для vuex-дебагера. Спілкування програми зі сховищем відбувається через виключно дії. У моїй програмі будь-яка дія, навіть синхронна, завжди повертає проміс. Мені здається, що свідомо вважати всі дії асинхронними (і працювати з ними як із промісами) простіше, ніж пам'ятати що є що.
5. Обмеження реактивності
Іноді буває, що дані у сховищі не змінюються. Наприклад, це можуть бути результати пошуку об'єктів на карті, запрошені із зовнішнього api. Кожен результат - це складний об'єкт з безліччю полів та методів. Потрібно виводити перелік результатів. Потрібна реактивність списку. Але дані всередині самих об'єктів постійні, і нема чого відстежувати зміну кожної властивості. Для обмеження реактивності можна використовувати Object.freeze.
Але я віддаю перевагу більш тупому методу: нехай state зберігає тільки список id-шників, а самі результати лежать поруч у масиві. Типу:
Щось у мене вийшло не настількигарно, як хотілося. Ось мої питання до спільноти:
— Як перемогти css анімації складніше за зміну opacity? Найчастіше хочеться анімувати появу якогось блоку невідомих розмірів, тобто. змінити його висоту height: 0 до height: auto. Це легко вирішується з JavaScript - просто обертаємо в контейнер з overflow: hidden, дивимося висоту оберненого елемента і анімуємо висоту контейнера. Це можна вирішити через CSS?
— Шукаю нормальний спосіб роботи з іконками в webpack, поки що безуспішно (тому продовжую користуватися fontello). Подобаються іконки whhg. Витяг svg, розбив на файли. Хочу вибрати кілька файлів та автоматично збирати в inline шрифт + класи на основі назв файлів. Чим це можна робити?
Хардкорна конфа за С++. Ми запрошуємо лише профі.