В прошлый раз, когда мы делали to-do на Alpine.js, меня очень сильно расстроило, что, хоть я и могу создавать вложенные компоненты, я не могу получать данные из родителя. Через какую-нибудь переменную, $parent, например. Поэтому мне пришлось запихивать все яйца в одну корзину. Свойства и методы, отвечающие за добавление новых задач, перемешались со всеми остальными. Я хотел выделить отдельный компонент, но необходимость доступа к массиву todos меня ограничивала. Если вы подумали, что это не очень хорошо, то вы не правы. На самом деле, это ужасно. Всё, расходимся? Нет. Я еще раз полистал документацию и вспомнил про магическое свойство $dispatch. Ну, конечно… однопоточная связь, проброс событий. Ну давайте попробуем. А потом еще переосмыслим всё с глобальным store. Для начала откроем наш код. Первое, что надо сделать, – превратить обертку над input в компонент и перенести в него inputValue. Естественно, наш новый компонент понятия не имеет, что такое addTodo(). Ему и не надо. Вместо этого мы будем диспатчить CustomEvent. Мы диспатчим событие add и передаем ему значение inputValue как payload (в спецификации он называется detail). Теперь это событие надо принять. Примет его наш корневой компонент. И вызовет addTodo() с нашим inputValue. Осталось немного поправить addTodo() и готово. Всё круто, вот только теперь inputValue не отчищается. В этой функции, естественно, мы это сделать не можем. Это нужно делать внутри компонента. А можно?.. Нет. $dispatch доступен только в разметке. В принципе, так тоже терпимо. Если бы нам нужно было делать больше логики, мы могли бы вместо inputValue = '' вызвать конкретную функцию, которую бы определили в <script>. А можно?.. Ну конечно можно! Несмотря на то, что $dispatch недоступен нам, в отличие от остальных магических свойств, в this компонента, мы можем использовать хитрость и передать его как параметр функции. Для удобства выделим функцию в <script> для нашего внутреннего компонента. Template теперь выглядит так. Код на данный момент У нас получилось перенести inputValue в отдельный компонент. Неплохо, но хотелось бы и addTodo() перенести. Как сделать это? На самом деле, элементарно. Просто передайте в detail вместо inputValue уже готовый объект to-do и запушьте его в todos. А знаете что? Попробуйте сами. Сделаем статью более обучающей :) И не забудьте отчистить inputValue после. Вот как я это сделал. Вы, наверное, заметили, что в заголовке заявлена еще одна тема. Всё, что мы делали выше, – это, конечно, круто. Но масштабируемость сильно хромает. И рано или поздно придет мысль: "Было бы круто все данные хранить в отдельном store, как это делает Redux/Mobx/Vuex и т.п. И обращаться уже к нему, не прокидывая ничего вверх без надобности." Знакомьтесь, Spruce. 2 килобайта чистой годноты. Вернемся к оригинальному коду и сделаем все по новому. Для начала подключим, а разберемся по ходу. До Alpine.js и удалить у Alpine defer. Такую цену придется заплатить. Довольно дешево, я беру. Теперь подписываем наш корневой компонент к store. Данные внутри компонента нам больше не понадобятся. Spruce дает нам одноименную переменную в window. Чтобы создать store, используется Spruce.store(<название>, <объект>). *store может быть неограниченное количество. Теперь, чтобы дотянутся до значений, можно использовать $store.data.todos. Сделаем разделение по Vue Options API – данные будем хранить в data, а методы – в methods. Слышите этот звук? Это скрипят зубы у евангелистов иммутабельности. Здесь её нет, она и не нужна, так как масштабы не те. Иммутабельность сложнее, требует больше написанного кода и очень редко когда приносит реальную пользу. Сложность точно не уровня Alpine. Ну и, собственно, наш template. Последний штрих – нужно получить данные с API. В Spruce для этого есть удобный метод. Если Spruce.on(<событие>, <колбэк>) предназначен для навешивания событий, то Spruce.once(<событие>, <колбэк>) как раз для выполнения какого-то действия один раз. Событие init – то, что мы ищем. Mission accomplished. Вот полный код. Можно легко превратить наш Options API в "Composition API", где каждый store отвечает за конкретную функциональность. Делите ваши данные, как заблагорассудится. При этом нам не нужны внутренние компоненты и проброс данных через события, так как все компоненты имеют равный доступ к $store. Актуально на фоне борьбы с архитектурами master/slave :) Полезные ссылки: *Photo by Jakub Kapusnak on Unsplash Web-разработчик