При создании форм на React встает вопрос управления состоянием приложения. Казалось бы, богатый выбор, однако Redux поражает своим «fizzbuzz enterprise», а Mobx сайд-эффектами в ООП коде от начинающих разработчиков. Исходя из моего опыта, оба инструмента плохо подходят для онбординга начинающих программистов. А бизнесу их трудоустроить очень выгодно: они дешевые, пугливые и наивные
Решением проблемы будет выкидывание лишних абстракций из кодовой базы Frontend. Бизнес-логика, размазанная на несколько файлов, вгоняет начинающего разработчика в ступор. Я хотел бы поделиться одной хорошо зарекомендовавшей себя практикой.
Давным-давно, в далёкой-далёкой галактике…
На момент 2010-ого года, когда управление состоянием веб-приложения переходило от ненаправленного потока данных (jQuery) к контейнерам состояния, первые фреймворки широко использовали разновидности паттерна MVVM (AngularJS, MarionetteJS, BackboneJS). Дело в том, что на тот момент Microsoft пытались захватить рынок и широко продвигали Silverlight, разновидность WPF для интернета. И бой был на смерть, к примеру, язык разметки XAML первым ввел нечто похожее на FlexBox и CSS Grid (см. StackPanel и Grid). Веб копировал наработки мелкомягких по максимуму.
Ошибка, которая привела к смерти Backbone
Подход Backbone к управлению состоянием приложения подразумевал использование сущности Collection, которая автоматически синхронизирует содержимое массива с CRUD на стороне backend.

Изменение объекта, который лежит внутри Collection, порождало перерисовку приложения. Идея хорошая, однако:
-
Прикладной программист должен наследовать свой класс от Collection, переопределив внутри методы для обращения к backend. Наследование, как порождение лишней абстракции, антипаттерн для frontend, нужно использовать композицию.
-
Collection предоставлял более 10 методов к переопределению для синхронизации содержимого с backend. Как правило, переопределяли только одну функцию
syncCollection -
При передаче доработки фичи от одного программиста к другому, добавлять новые кейсы в функцию для синхронизации коллекции было сложнее с каждым разом. Это связано с тем, что нужно сохранить обратную совместимость с текущим кодом
Что можно сделать?
Можно оставить на Collection только задачу перерисовки UI, вынеся запросы к backend на чтение и запись в обработчики действий пользователя до мутации данных
import { useCollection } from "react-declarative"; const ListItem = ({ entity }) => { const handleIncrement = () => { /* await fetch(`/api/v1/counters/${entity.id}`, { method: "PATCH", headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ counter: entity.data.counter + 1 }) }); */ entity.setData({ id: entity.id, counter: entity.data.counter + 1 }); }; return ( <div key={entity.id}> {entity.data.counter} <button onClick={handleIncrement}>Increment counter</button> </div> ); }; export const App = () => { const collection = useCollection({ onChange: (collection, target) => console.log({ collection, target }), initialValue: [] // await fetch() or props... }); const handleAdd = () => { /* const { id, ...data } = await fetch("/api/v1/counters", { method: "POST", }).then((data) => data.json()); */ collection.push({ id: Math.max(...collection.ids, 0) + 1, counter: 0 // ...data }); }; return ( <> {collection.map((entity) => ( <ListItem key={entity.id} entity={entity} /> ))} <button onClick={handleAdd}>Add item</button> </> ); }; export default App;
Код, представленный выше, выводит список счетчиков (демо на codesandbox). Внутри кода закомментированы места, где можно обратиться к серверу. Также возможно обработать исключение в запросе так, что блок catch будет выполнен в контексте формы. Например, это можно использовать, чтобы вывести snackbar из хука notistack
import { useSnackbar } from 'notistack'; ... const { enqueueSnackbar } = useSnackbar(); const handleNetworkRequest = () => { fetchSomeData() .then(() => enqueueSnackbar('Successfully fetched the data.')) .catch(() => enqueueSnackbar('Failed fetching data.'));
Массив, содержимое которого требуется синхронизировать с backend, нужно положить в аргумент initialValue хука useCollection (строка 38). Хук вернет объект Collection, реализующий метод map, позволяющий бесшовно с массивом вывести список элементов. Каждый элемент массива будет обернут в контейнер Entity, предоставляющий доступ к оригинальному значению через свойство data и метод setData. Вызов метода setData (строка 17) синхронно изменит data и, через debounce, попросит хук useCollection перерисовать форму
Как синхронизировать один объект?
По аналогии с useCollection, экспортируется хук useEntity. Он вернет переданный в аргументы объект, обернутый в Entity, вызов setData у последнего также перерисует форму.

Используя два вышеупомянутых хука можно сэкономить на передаче обратных вызовов через props, убрать лишний boilerplate, не терять контекст исполнения при разбиении взаимосвязанной бизнес-логики на разные файлы
Где посмотреть код хуков?
Чтобы убрать нужду плодить зависимости в вашем проекте, предоставляю ссылки на файлы)
ссылка на оригинал статьи https://habr.com/ru/post/687568/
Добавить комментарий