[JavaScript, Функциональное программирование] Lens JS как менеджер состояния приложения
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Обзор библиотеки lens-js и эксперименты с котиками.Данные — это, в действительности, важная часть Вашего будущего приложения или отдельной библиотеки. Важна их структура, целостность, а также и подходы к организации их хранения и обработки. Задача, прямо скажем, не тривиальна, особенно в масштабах корпоративных проектов. Одно из решений — использование менеджера состояния, об одном из которых, тут пойдёт речь.ЛинзыИ так, что же такое «Линзы»? Проще всего ответить тезисно - линзы это:
- принцип организации работы с данными, где те квантуются по отдельным узлам в одном большом направленном графе;
- агрегатор (редьюсер), который занимается сборкой всех отдельных квантов по всем правилам функциональной парадигмы;
- интерфейс, который обеспечивает доступ к данным каждого кванта;
- и последнее, линза обеспечивает целостность данных и их актуальность в Вашем приложении.
Тут вот стоит отметить, что мы ещё не говорим о как-либо реализации. Линза — это хорошая сказка для тех, кто уже устал от Redux, MobX и т. п. или разрабатывает какой-нибудь специфический модуль, где проблематично использовать популярные менеджеры состояний. Реализаций линз — множество. Попробуйте их все!
Как это всё работает?Чем объяснять на пальцах, гораздо проще сделать это на каком-нибудь примере. Будем экспериментировать на котиках. Не волнуйтесь! Ни один котик не пострадает! Приступим…Рассмотрим демонстрационное приложение react-lens-cats, разработанное на основе линз. В нём используется библиотека — lens-js, а также её обёртки для TypeScript и React — lens-ts и react-lens.Займёмся постановкой задачи: Представим, что несколько котиков решили прокатиться на карусели. Нам нужно организовать их очередь и распределить занятые места между ними. Формально — это два массива, между которыми котики будут перемещаться.Пусть структура данных выглядит следующим образом:
export interface Cat {
name: string;
}
export interface Queue {
cats: Cat[]
}
export interface Store {
street: Queue;
circle: Queue;
}
Давайте создадим наше состояние и объявим линзу в фале lens.ts
import { Lens } from '@vovikilelik/react-ts';
const murzic: Cat = { name: 'Murzic' };
const pushok: Cat = { name: 'Pushok' };
const sedric: Cat = { name: 'Sedric' };
const rizhik: Cat = { name: 'Rizhik' };
const store: {lens: Store} = {
lens: {
street: { cats: [murzic, pushok, sedric, rizhik] },
circle: { cats: [] }
}
};
export const lens = new Lens<Store>(
() => store.lens,
(value, effect) => {
store.lens = value;
effect();
}
);
Пока всё легко и понятно? Но не будем отвлекаться, а то получится как на "матане".Как можно заметить, мы экспортируем только константу lens, относительно которой и будем работать с состоянием. Это синглтон, "детка".Будет не так интересно, если мы просто пойдём по приведённому исходному тексту. Давайте попробуем разобраться с самых азов? В качестве эксперимента, создадим Test.tsx и попробуем получить доступ к свойствам нашего состояния.
import { lens } from './lens';
export Test: React.FC = () => (
<div>
{ lens.go('circle').go('cats').get().map(c => c.name).join(' ') }
{ lens.go('street').go('cats').get().map(c => c.name).join(' ') }
</div>
);
Теперь мы можем видеть имена наших котиков. Выражение выглядит пугающе, но в процессе заполнения можно заметить, что благодаря типизации, имена полей предлагаются средой разработки, что весьма удобно.
Автоподстановка вложенных свойствНо постойте, судари! У нас тут что-то не так с кодом! И действительно, массивы circle и street одинаковы. Выгоднее получить универсальный компонент, который отображает только массив котиков, откуда бы мы его не взяли. А давайте, просто, передавать узел линзы, как параметр компонента, вот так:
import { Lens } from '@vovikilelik/lens-ts';
import { useLens } from '@vovikilelik/react-lens';
import { Cat } from './lens';
export Cats: React.FC = (cats: Lens<Cat[]>) => {
const [catsArray] = useLens(cats);
return (
<div>
{ catsArray.map(c => c.name).join(' ') }
</div>
);
}
Тогда файл Test.tsx будет выглядеть следующим образом:
import { lens } from './lens';
export Test: React.FC = () => (
<div>
<Cats cats={lens.go('circle').go('cats')} />
<Cats cats={lens.go('street').go('cats')} />
</div>
);
Всё ещё сложно. Для чего мы постоянно обращаемся к корневой линзе? Нам же не так и важно, где хранятся массивы котиков? Давайте сделаем Test.tsx более универсальным, ну и назовём его как-нибудь подходяще - Lunapark.tsx:
import { Lens } from '@vovikilelik/lens-ts';
import { Queue } from './lens';
export Lunapark: React.FC = (street: Lens<Queue>, circle: Lens<Queue>) => (
<div>
<Cats cats={street.go('cats')} />
<Cats cats={circle.go('cats')} />
</div>
);
Другое дело. Как ловко мы тут всё инкапсулировали...Теперь вид компонента упростился. Но смотрите, проявилась одна особенность, таким образом, мы можем создавать целые модели компонентов, которые будут также универсальны, как и рядовые.
И действительно, поскольку каждый узел линзы является объектом, адресующим некие данные в глобальном состоянии, то его передача в качестве аргумента ничем не отличатся от передачи простого значения. Однако в случае линз, мы уже получаем механизм отслеживания изменений в узле по средствам useLens.
Теперь дело за малым, переместить котиков из одного массива в другой. Давайте в нашем Lunapark.tsx создадим кнопку с обработчиком, где мы и завершим нашу задумку.
const popCat = (lens: Lens<Cat[]>): Cat | undefined => {
const cats = lens.get();
const cat = cats.pop();
lens.set(cats);
return cat;
}
const playCat = (lens: Lens<Cat[]>, cat: Cat) => {
lens.set([...lens.get(), cat]);
}
export Lunapark: React.FC = (street: Lens<Queue>, circle: Lens<Queue>) => {
const onCatPlay = useCallback(() => {
const cat = popCat(street.go('cats'));
cat && playCat(circle.go('cats'), cat);
}, [street, circle]);
return (
<div>
<Cats cats={street.go('cats')} />
<Cats cats={circle.go('cats')} />
<button onClick={onCatPlay} />
</div>
);
}
Обратите внимание, что обработка изменений уже реализована внутри линзы по средствам useLens, т. е. после изменения данных в узлах cats, связанные компоненты будут обновлены, и внимание, магия — только эти компоненты, что полезно для производительности.
Что же, получилось весьма недурно.А есть ещё какая-нибудь польза?Линзы — это не строго привязанная, к какой-либо технологии, библиотека. Их успешно можно применять там, где не подходят привычные подходы. Например, при создании динамических сцен на BabylonJS или внутреннего состояния Web-компонентов и т. п. Линзы — это не аналог Redux или похожих технологий и, как следствие, могут применяться совместно, как абстракция более низкого уровня. Всё дело в воображении…ВыводыЧто, уже? Да. Целью статьи было познакомить читателя с ещё одним подходом для организации состояния приложения. Мы рассмотрели самое-самое главное — потенциал к масштабируемости и области применения.Давайте подытожим! С помощью линз можно:
- реализовывать состояние приложения;
- разрабатывать общие компоненты и модели;
- организовывать событийно-ориентированные подпрограммы;
- создавать абстракции для работы с другими подходами к управлению состоянием.
Ссылки
- Wiki по lens-js
- Проект с котиками на lens-js
- Пакет react-lens на npm
- Пакет lens-ts на npm
- Пакет lens-js на npm
- Статья про другие линзы
- Ещё интересный пост про линзы
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, Разработка игр, Логические игры] DagazServer: Встречайте Garbo Chess
- [JavaScript, Node.JS, Meteor.JS] Мажорный MeteorJS 2.0: HMR, Cloud и другое
- [Ajax, PHP, JavaScript, Программирование] Ajax, REST API OpenCart
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] Изучаем React: 300+ вопросов для подготовки к собеседованию
- [JavaScript, TensorFlow] Фронтендер пишет нейронки. Уровень сложности «мартышка и уравнение Беллмана»
- [Node.JS, TypeScript] Creating Node.JS web server application with Express, Typescript, Jest, Swagger, log4js and routing-controllers (перевод)
- [JavaScript, Node.JS, API] Создание самодокументирующегося сервера на Node.JS
- [PHP, JavaScript, Программирование, Symfony] Новое в Symfony: инициатива UX — новая экосистема JavaScript для Symfony (перевод)
- [Open source, JavaScript, Программирование, Визуализация данных] Новый график на Moiva.io с данными от #StateOfJS
- [JavaScript, Node.JS] Авторизация и аутентификация на NodeJs и Socket.io и проблемы вокруг
Теги для поиска: #_javascript, #_funktsionalnoe_programmirovanie (Функциональное программирование), #_linzy (линзы), #_lens, #_react, #_typescript, #_javascript, #_state_management, #_javascript, #_funktsionalnoe_programmirovanie (
Функциональное программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:57
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Обзор библиотеки lens-js и эксперименты с котиками.Данные — это, в действительности, важная часть Вашего будущего приложения или отдельной библиотеки. Важна их структура, целостность, а также и подходы к организации их хранения и обработки. Задача, прямо скажем, не тривиальна, особенно в масштабах корпоративных проектов. Одно из решений — использование менеджера состояния, об одном из которых, тут пойдёт речь.ЛинзыИ так, что же такое «Линзы»? Проще всего ответить тезисно - линзы это:
Тут вот стоит отметить, что мы ещё не говорим о как-либо реализации. Линза — это хорошая сказка для тех, кто уже устал от Redux, MobX и т. п. или разрабатывает какой-нибудь специфический модуль, где проблематично использовать популярные менеджеры состояний. Реализаций линз — множество. Попробуйте их все!
export interface Cat {
name: string; } export interface Queue { cats: Cat[] } export interface Store { street: Queue; circle: Queue; } import { Lens } from '@vovikilelik/react-ts';
const murzic: Cat = { name: 'Murzic' }; const pushok: Cat = { name: 'Pushok' }; const sedric: Cat = { name: 'Sedric' }; const rizhik: Cat = { name: 'Rizhik' }; const store: {lens: Store} = { lens: { street: { cats: [murzic, pushok, sedric, rizhik] }, circle: { cats: [] } } }; export const lens = new Lens<Store>( () => store.lens, (value, effect) => { store.lens = value; effect(); } ); import { lens } from './lens';
export Test: React.FC = () => ( <div> { lens.go('circle').go('cats').get().map(c => c.name).join(' ') } { lens.go('street').go('cats').get().map(c => c.name).join(' ') } </div> ); Теперь мы можем видеть имена наших котиков. Выражение выглядит пугающе, но в процессе заполнения можно заметить, что благодаря типизации, имена полей предлагаются средой разработки, что весьма удобно.
Автоподстановка вложенных свойствНо постойте, судари! У нас тут что-то не так с кодом! И действительно, массивы circle и street одинаковы. Выгоднее получить универсальный компонент, который отображает только массив котиков, откуда бы мы его не взяли. А давайте, просто, передавать узел линзы, как параметр компонента, вот так: import { Lens } from '@vovikilelik/lens-ts';
import { useLens } from '@vovikilelik/react-lens'; import { Cat } from './lens'; export Cats: React.FC = (cats: Lens<Cat[]>) => { const [catsArray] = useLens(cats); return ( <div> { catsArray.map(c => c.name).join(' ') } </div> ); } import { lens } from './lens';
export Test: React.FC = () => ( <div> <Cats cats={lens.go('circle').go('cats')} /> <Cats cats={lens.go('street').go('cats')} /> </div> ); import { Lens } from '@vovikilelik/lens-ts';
import { Queue } from './lens'; export Lunapark: React.FC = (street: Lens<Queue>, circle: Lens<Queue>) => ( <div> <Cats cats={street.go('cats')} /> <Cats cats={circle.go('cats')} /> </div> ); И действительно, поскольку каждый узел линзы является объектом, адресующим некие данные в глобальном состоянии, то его передача в качестве аргумента ничем не отличатся от передачи простого значения. Однако в случае линз, мы уже получаем механизм отслеживания изменений в узле по средствам useLens.
const popCat = (lens: Lens<Cat[]>): Cat | undefined => {
const cats = lens.get(); const cat = cats.pop(); lens.set(cats); return cat; } const playCat = (lens: Lens<Cat[]>, cat: Cat) => { lens.set([...lens.get(), cat]); } export Lunapark: React.FC = (street: Lens<Queue>, circle: Lens<Queue>) => { const onCatPlay = useCallback(() => { const cat = popCat(street.go('cats')); cat && playCat(circle.go('cats'), cat); }, [street, circle]); return ( <div> <Cats cats={street.go('cats')} /> <Cats cats={circle.go('cats')} /> <button onClick={onCatPlay} /> </div> ); } Обратите внимание, что обработка изменений уже реализована внутри линзы по средствам useLens, т. е. после изменения данных в узлах cats, связанные компоненты будут обновлены, и внимание, магия — только эти компоненты, что полезно для производительности.
=========== Источник: habr.com =========== Похожие новости:
Функциональное программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:57
Часовой пояс: UTC + 5