[JavaScript, ReactJS] 5 причин использовать LesnJs

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
03-Июл-2021 10:30

В данной статье будут рассмотрены: библиотека lens-js и её обёртки lens-ts и react-lens. Мы попробуем сравнить их с популярными менеджерами состояний (Redux и MobX/MST), а также объясним преимущества и недостатки.
Стоит отметить, что "Линзы" - это только концепция. Мы же будем рассматривать конкретную реализацию. Поэтому, нужно понимать, что другие библиотеки, реализующие "Линзы" могут оказаться лучше или чуть-чуть похуже.
Что такое линзы"Линзы" - это концепция функционного программирования, позволяющая решать задачи управления состоянием приложения. Внешне, это большой ориентированный граф, где каждый из узлов является интерфейсом (или контроллером) своего сегмента данных, а сам граф - это правило (или агрегатор), описывающее полное состояние приложения."Линзы" реализуются при помощи "геттеров" и "сеттеров", которые хранят процедуры того, каким образом нужно присоединить отдельный узел ко графу.Более подробнее о линзах и библиотеке lens-js можно узнать в WikiСравнение с ReduxRedux прекрасная и функциональная библиотека с низким порогом вхождения, имеет большое комьюнити и пользуется огромной популярностью. Что же, на этом всё, статья закончена, выводы... до свидания...Ну нет, мы собрались не для того, чтобы просто "сложить лапки". Давайте сначала обратим внимание и на слабые стороны Redux, а именно:
  • Гиперизбыточность.
  • Сложность адаптации новых сотрудников на проекте.
  • Плохая переносимость форм (неуниверсальность)
  • Производительность
Архитектурно, Redux реализует работу с состоянием в отдельных агрегаторах (reducer), управляемыми особыми действиями - action. Этот код расположен отдельно и обрабатывается независимо от места и времени вызова. Всё это формирует большой аспект применимый ко всему состоянию приложения. Не АОП, но недостатки, свойственные такому подходу имеются (мы же тут, чтобы немного покритиковать?).Во-первых, аспект не может обладать полной информацией о контексте изменений, в связи с чем, для малоразличающихся действий приходится создавать отдельные обработчики. Иногда, различающиеся только одной строчкой.
Часто приходилось видеть, что для асинхронной работы страницы использовались аж целых 5 действий: INIT, LOADING, LOADED, SECCSES, ERROR. Для всего этого создавались "редьюсеры".
Во-вторых, аспект формирует отдельную подархитектуру, со своими особенностями, знания о которой нужно хранить и передавать. Другими словами, умения (даже хорошо) работать с Redux крайне недостаточно.В-третьих, чем "больше" аспект аспект, тем сильнее он способствует сильной зацеплённости и низкой связности сущностей. Это, к примеру, может повредить общей архитектуре приложения (см. GRASP).А как с этим справляются "линзы"?Давайте вспомним, что по сути, "линзы" - это граф, который является правилом агрегации состояния. Если в Redux мы эти правила описывали в "редьюсерах", то "линза" создаёт их автоматически. Работает это по двум принципам:
  • Статистический - с высокой вероятностью, мы не будем хаотично менять типы данных и/или способы их обработки, а напротив - постараемся их придерживаться.
  • Структурный - если сущность B является подструктурой A, а сущность D подструктурой C, то с высокой вероятностью они сохранят свои отношения, т. е. маловероятно, что B станет подструктурой C, при условии, что B и D не взаимозаменяемы.
Пусь только вероятностно, но это уже позволяет накопить достаточно информации для автоматической сборки состояния. Рассмотрим пример на линзах:
/* Мы хотим получить цвет бантика у котика */
const color = cat.go('ribbon').go('color');
Сложно представить иную структуру для этого. Однако для того чтобы записать новый цвет, мы должны собрать объект обратно. Хорошая новость в том, что получая дочерние узлы, мы сохраняли информацию о структуре. "Линза", как бы, уже подготовила для нас агрегатор для узла color.
/* Теперь поменяем цвет */
color.set('#FF0000');
Готовый объект будет иметь следующий вид:
{ ribbon: { color: '#FF0000' } }
Изначально, мы специально упустили способ получения узла cat. Это только имя переменной, связанной с неким узлом. Мы могли бы взять его из массива или другого поля, которое имело бы семантически-подобную структуру, например:
{
  murzik: { ribbon: { color: '#0000FF' } },
  pushok: { ribbon: { color: '#FF0000' } }
}
Тогда код стал бы более универсальным:
function setColor(cat, color) {
  cat.go('ribbon').go('color').set(color);
}
setColor(cats.go('murzik'), '#FF0000');
setColor(cats.go('pushok'), '#0000FF');
А вот так, мы бы добавили Рыжика с ленточкой, как у Мурзика:
const murzik = cats.go('murzik');
const rizgik = cats.go('rizgik');
rizgik.go('ribbon').set(
  murzik.go('ribbon').get()
);
Как видно из примеров, характер манипуляций очень схож и не требует специфических агрегаторов. Весь представленный код - это действительно всё, что потребуется для работы над подобными объектами.
"Линза" не привязывает к автоматизированному способу склейки данных, а только использует его по умолчанию. Вы можете использовать различные "мапперы". Подробнее можно узнать тут.
Целая "Линза", хоть и является графом, но благодаря поддержке принципа "каждый узел главный", не нужно учитывать структуру до узла. Давайте сравним:
/* Код на redux */
const Cat = ({ cat }) => <div>{cat.ribbon.color}</div>;
const mapStateToProps = (state, ownProps) =>
  ({cat: state.cats[ownProps.name]});
export default connect(Cat, mapStateToProps);
/* Код на линзах */
export const Cat = ({ lens }) => {
  const [ cat ] = useLens(lens);
  return <div>{cat.ribbon.color}</div>;
}
Из кода видно, что Redux, хоть и позволил нам выбирать конкретного котика, но жёстко привязал компонент к одному списку. Для попытки взять котика из другого списка, нам пришлось бы вводить второе измерение и т. д. Линзы же ожидают другой подход, где необходимый котик будет взят в компоненте родителя, т. е. вектор данных определяется адаптивно, а не хардкодом, а это делает "линзовый" компонент более универсальным и пригодным к работе на других формах, без необходимости его переделывать.В отличие от Redux, линза вызывает перерисовку только тех компонентов, которые были связанны с изменившимся узлом. Это работает даже, если узел хранит объект. С одной стороны, линза тратит ресурсы на просчёт изменений, с другой - пересчёт виртуального DOM и манипуляции с DOM браузера более тяжёлые.Сравнение с MobX/MSTMST позволяет использовать MobX в едином дереве, органично реализуя работу над всем состоянием приложения. Каждый узел - это многофункциональный контроллер, который позволяет организовывать разноплановую работу с данными узла, делать фоновые запросы и... и так далее...
Использование MST похоже на ход "E2-E4", на большую пивную кружку для эспрессо. Если действительно есть необходимость так упиться кофем, то нет никаких претензий. В остальных случаях это кажется очень нагромождённым.
Между "Линзой" и MST можно разглядеть общие моменты. Но есть же и отличия. Первое из них - это отсутствие необходимости создавать каждый контроллер отдельно, т. е. часть процесса построения дерева "Линза" берёт на себя.
/* Код на MST */
export const Ribbon = type
  .model("Ribbon", {
    color: type.string
  })
  .actions(self => ({
    setColor(value) {
      self.color = value;
    },
  }));
export const Cat = type
  .model("Cat", {
    ribbon: type.reference(Ribbon)
  });
/* Код на линзе (для такого же функционала) */
const cat = cats.go('murzik');
Разумеется, для разных технологий сложно определить точный эквивалент в исходном тексте, вне контекста конкретного приложения, но... это весьма близко...
Весь фокус в том, что "линза" будет ориентироваться относительно структуры данных, а не передаваемого типа. Потому, нет необходимости этот тип указывать явно. Например, если у котика появится ещё и шапочка, то появится и возможность с ней работать.
/* Шапочки ещё нет, но мы хотим узнать, когда она появиться */
cat.go('cap').attach(() => console.log('Cap is here!'));
/* Теперь оденем шапочку */
cat.go('cap').set({ color: 'black' });
/* Увидим в консоли */
> Cap is here!
С другой стороны, если мы определили, что у котика просто не может быть шляпки, то "Линза" не даст ничего сделать.
interface Cat {
  ribbon: { color: string };
}
const getCats = (): Lens<Cat[]> => ... /* Как-то получили котиков */
const cat = getCats().go(0);
const cap = cat.go('cap'); // Выдаст ошибку
Недостатком контроллера по умолчанию, в данном случае, будут отсутствие следующих возможностей:
  • Расширять контроллер в каждом конкретном случае. Однако и тут срабатывает принцип статистичности. Из множества методов для работы состоянием есть много похожих, например "геттеры" и "сеттеры", определив которые можно покрыть значительную часть функциональности.
  • Обеспечение инкапсуляции. Стоит отметить, что инкапсуляция это не инструмент защиты исполняемого кода, а подход к проектированию, т.е. можно просто договориться, какие поля не трогать =)
Стоит отметить, что "линзы", всё же, имеют возможность расширятся в парадигме ООП и приближаться к функциональности к MST. Подробнее можно прочесть тут.
Резюме...ну наконец то!Давайте подведём итоги, добавим ещё несколько общих плюсов и составим список причин, всё же, использовать "Линзы".
  • "Линзы" весьма компактны по сравнению с Redux и MobX/MST.
  • "Линза" сохраняет (по крайней мере, пытается) производительность приложения.
  • "Линза" позволяет создавать универсальные компоненты, независящие от конкретных форм и, даже, технологий. Вы смело можете сделать свой фреймворк - линзы будут работать и там.
  • "Линза" умеет в ООП. Может работать с типами данных, а также масштабироваться.
  • "Линза" не тянет за собой зависимости (за исключением обёрток). Это очень небольшая и монолитная библиотека.
Постой! Ну есть же "скелеты в шкафу"?Ох, думал не спросите...У "линзы" есть два существенных недостатка, на данный момент.
  • Отсутствие большого числа надстроек, промежуточного ПО и смежных библиотек. Однако выходом их этой ситуации может стать комбинирование подходов к организации состояния. Например, в качестве состояния форм можно использовать Redux, а компоненты сделать на "Линзах". Это позволит использовать промежуточное ПО (Saga, например), избавит от избыточности и сделает код более универсальным и переносимым. Мега-комбо!
  • Ограничение при серверном "рендеринге". "Линза" потребует специальной оснастки, которую придётся писать самостоятельно. Стоит отметить, что речь идёт только о библиотеке lens-js, а не о всех "линзах" в целом.
Удачи в экспериментах, друзья!
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_javascript, #_reactjs, #_react, #_lens, #_redux, #_mobx, #_mobxstatetree, #_javascript, #_reactjs
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 11-Май 01:12
Часовой пояс: UTC + 5