Как стать автором
Обновить

Комментарии 12

Спасибо за вредные советы.

Здравствуйте.

Это не реклама эффектора) И я не говорю, что используйте его) Причина почему примеры на нем - они простые и понятные. Я лишь упомянул, что с его помощью это легко реализуется. Есть другие инструменты.

Всё что мне нужно от эффектора: createStore, createEffect. Всё. Для реакта еще из effector-react импортировать хук useUnit.

Но спасибо за видос, гляну) Полезно, думаю, будет)

Зачем в примере с effecror делать такой финт ушами с добавлением состояния запроса в стор, когда у эффектов есть состояние pending?

Здравствуйте.

1. Я не знаю эффектор так хорошо. В первую очередь я взял его потому что на нем можно легко и быстро реализовать то что я имел ввиду, и я взял только тот функционал который мне необходим (createStore, createEffect), а для этого "pending" не нужен.
2. То как эта идея будет реализована - не важно, это уже относится к реализации, а я предлагаю абстракцию, поток данных. Так же отслеживание конкретного эффекта выбивается из идеи.
3. Подписываться на конкретный эффект, в каких то случаях, действительно может быть лучше. В том же редаксе, можно сделать dispatch.unwrap и получить промис, состояние которого можно отслеживать. Но, допустим я решил так же делать todoAdding в true когда у меня вызывается эффект getTodos. Тогда мне нужно будет идти в тот(те) компонент(ы) где я отслеживаю этот effect и менять там что-то.. А это уже именно то от чего я и хочу полностью избавиться..
С todoAdding плохой пример, потому что это буквально стор для добавления новой задачи, и, скорее всего, у нас будет только один такой эффект который будет добавлять задачу, тут с вами согласен.

Я пытался создать максимально простые примеры содержащие только суть моей идеи. UI просто рендерит данные и вызывает экшены. Без отслеживания состояния этих экшенов и всего такого. (да, для оптимизации иногда будет лучше отслеживать экшены которые вызываются, но это достаточно редкие случаи, от которых лучше отказаться, если есть возможность)

Фронтендеры с этими многочисленными стейт-менеджерами из пустого в порожнее уже лет 15 переливают... и везде в примерах плохо работающая тудушка...

Минусы? Пока не обнаружены.

Минус вашего и многочисленных других подходов например в отсутствии синхронизации стейта между вкладками. Открыл пользователь пару вкладок с приложением - в одной например задачу добавил в тудушку - закрыл - в соседней вкладке все по старому. Если где-то какие-то данные регулярно обновляются тащатся с сервера через long-pooling например - то сколько вкладок открыто - столько запросов и будет отправляться. Какой-то недоглобальный стейт получается. Тут sharedWorker в помощь и слушатели изменения localStorage.
Данные скорее всего одни и те же вытаскиваются по сто раз, не предусмотрено никакого кэширования. А что если еще и данных прям много? Нужна поддержка OPFS или может indexedDB. И как чистить стейт в таком случае от данных которые уже не нужны / не рендерятся больше?

Нет поддержки offline-mode - что если я хочу сидя в самолете набросать себе задач в туду-лист и чтобы они потом когда появится интернет отправились на сервер, а пока я бы мог их редактировать, может что-то бы уже отметил как выполненное пока летел.

Ну и пуш данных с сервера - чтобы если я с телефона что-то добавил - то оно в открытом сайте на компе отобразилось, опять же одного только сообщения через websocket тут не достаточно - крышка ноута то закрыта и он спит - нужно чтобы когда я открою ноут - сайт полез за обновлениями, а заодно отправил свои(ну те, из самолета).

Вот это всё сложные вопросы, не ясно как это лучше организовать, решений хороших и доступных просто нет, что-то платное(rxdb) или c vendor-lock(firebase) и еще с кучей своих ограничений.
И если это все внедрять, то есть делать просто нормальное приложение которое не вводит в ступор пользователя, все подобные подходы превращаются в запутанного монстра, они не масштабируются нормально до такого уровня. А если не делать - то простите, но просто забрать фетчем json с сервака и не запутаться в том в какую переменную его засунул ума много не надо так что и проблем требующих какого-то решения и особенного подхода тоже нет.

То есть основной минус - невозможность масштабировать данный подход до уровня нормального приложения с синхронизацией большим объемом данных итп.

Здравствуйте.

Я говорил о другом и этот подход никак с этими проблемами не связан, а писал я про взаимодействие UI, Model, Action. Всё. То есть то есть UI просто рендерит и вызывает эффекты. А модель сама себя обновляет подписками на эффекты.

Я просто предложил некую абстракцию на этом уровне и не дальше. Как это будет реализовано - это уже не важно, главное - принципы.

Но давайте рассмотрим все проблемы которые вы описали в рамках самого простого примера (плохо работающей тудушки).

Минус вашего и многочисленных других подходов например в отсутствии синхронизации стейта между вкладками

Я скажу больше. Тут об этом и других задачах - даже ни слова. Так что же с этим делать? Да куча есть всего. Как вы правильно написали - стореджи, воркеры, броадкасты, постпесседжи и что угодно.

Давайте представим, что у нас есть только хранилище статуса добавления задачи.

И в одной из вкладок мы начали добавлять задачу, пошел процесс. Другие вкладки про это ничего не знают, согласен.

Мы берем и из вкладки которая начала процесс - отправляем через, например, броадкаст это уведомление.

Другие вкладки получают это сообщение, понимают что это такое и выполняют какой-то свой экшен, на который подписана модель. И обновляет свое состояние.

// Модель во всех вкладках

export const todoAdding = createStore(false)
  .on(addTodoEffect, () => true)
  .on(addTodoEffect.finally, () => false);

Как мы свяжем первичный экшен с отправкой уведомления - не важно. Мы для это можем например написать свой интерфейс или сделать обертку над экшеном или над эффектом итд. Опять же, это уже детали реализации.

Но в момент вызова effect-а у нас будет отправляться postMessage всем слушателям с типом сообщения и данными.

На другом конце провода (в другой вкладке) ловится это сообщение, берется тип сообщения и вызывается соответсвующий effect с этим payload. На этот экшен была подписана модель - модель обновляется.

Вот у нас есть общение между вкладками которое соответствует тому что я писал. Модель сама себя обновляет подпиской на эффекты. А UI просто отрендерит новые данные.

------------

Нет поддержки offline-mode

Ну тогда заменяем броадкаст на локалсторейдж и вот у нас есть локальное хранилище.

Разделяем экшены на 2. Один будет просто сохранять в LS, другой отправлять запрос на сервер.

Пихаем их в один, но на "запрос" делаем условие, что мы онлайн.

И вот мы получили поддержку offline. Так же понадобится сделать синхронизацию с сервером при появлении интернета.

Делаем отдельный экшен, который будет это делать. Брать данные из LS и отправлять на сервер. Если мы захотим - еще подпишемся и на него и будем тоже как-то показывать.

------------

Ну и пуш данных с сервера.

В примере с "социальной" сетью это даже реализовано. Есть SSE соединение с сервером через который идут все уведомления и о моих действиях и о действиях других пользователей (сообщения для меня, добавления в друзья итд) и я просто подписываюсь на определенные уведомления и вызываю соответствующие эффекты на которые подписана модель. Модель обновляет себя -> UI просто себя рендерит. Всё.

Вот вам как это было сделано у меня (хоть это и было сделано временно, но суть отражает)

Вызов thunk-ов: https://github.com/VanyaMate/product/blob/master/src/features/notification/hooks/useFriendsStoreUpdaterByNotifications.ts

Модель: https://github.com/VanyaMate/product/blob/master/src/app/redux/slices/friends/slice/friends.slice.ts

Приходит уведомление -> вызывается определенный thunk -> модель обновляет себя из-за подписки -> UI рендерит.

------------

И все эти задачи сами по себе не связаны с тем что я описывал в статье. К сожалению, видимо, плохо её написал и не очень много людей поняло что я имел ввиду вообще, судя по комментариям.

Тут не про эффектор, не про реакт, не про то как вам реализовать это всё. Тут принципы и абстракция. Всё.

Я вообще свой стор написал из 3-х функций store, effect и сombine . Больше и не нужно.

UI - рендерит
Model - сама себя обновляет
Action - просто выполняют какую то задачу

UI - не меняет стор, не вызывает редюсеры, а просто рендерит и вызывает action-ы (через эффекты). Эффекты это связь экшена и модели.

Почему я показал на react? Просто он самый популярный и очень понятный код.

Почему эффектор? Не такой популярный, но очень понятный код.

Почему плохо работающая тудушка? Очень простой пример.

------------

Просто попробуйте представить себе любой атомарный компонент (widget).

Вся его задача сводится к рендеру данных и вызову какого либо эффекта. Всё.

Вызываем действия, которые просто что-то делают. Например создают новую задачу. Как это будет сделано - не важно. Локал сторейдж или fetch или комбинация этих действий.. Мы просто подписываемся на начало этого действия и конец, с ожиданием того, что нам вернется новая задача которую модель сама в себя вставит, а UI получит автоматически новые данные и отрендерит их. Не важно где был вызван этот эффект. Через UI, из пуш уведомления, из какого-то евента.. Он вызвался - по подписке обновились данные - рендер. Всё. Это вся идея. Удобно. Понятно. Просто.

Cпасибо за подробный ответ! Я понял что нужно все что я описал реализовывать в модели, а статья у вас не про её внутренности, согласен.
Мне тяжело вот так в уме представить всё это, я не видел подобных реализаций и вот показалось что это превратится в неповоротливого монстра если добавить всё что вы описали для работы с данными, будет огромное количество экшенов, частично или полностью дублирующих друг друга, раздутый стэйт и т п. но да - наверно это можно сделать более-менее красиво всё

Экшенов будет столько сколько вам нужно и не больше. Они бы и так были, просто, возможно, как часть какого-то другого кода.. Пока что, к сожалению, не могу привести пример большого проекта с полностью только таким походом, но позже дам ссылку на пример, когда он появится.

Здравствуйте.

Я переписал проект с redux-а на этот подход со своим стором и вот как это будет примерно выглядеть:

Action:
Это просто запрос на сервер который возвращает данные.
https://github.com/VanyaMate/product/blob/master/src/app/action/friends/acceptFriendRequest/acceptFriendRequest.action.ts

Model:
Модель, с первого взгляда, может показаться страшной, но на самом деле всё просто.
https://github.com/VanyaMate/product/blob/master/src/app/model/friends/friends.model.ts

Давайте разберем один пример:

  1. Мы создаем эффект основанный на action. Мы получаем ту же самую функцию, с той же сигнатурой, но теперь мы подписаться на неё можем.

  2. Создаем список полученных запросов в друзья.

  3. Подписываем store на этот effect и готово

export const acceptFriendRequestEffect = effect(acceptFriendRequestAction);
export const $friendRequestsReceived = store<Array<DomainFriendRequest>>([])
    .on(acceptFriendRequestEffect, 'onSuccess', (state, { args: [ requestId ] }) => state.filter((request) => request.requestId !== requestId))

Есть store $friendRequestsReceived (список полученных запросов в друзья)
И при успешном завершении эффекта acceptFriendRequestEffect мы удаляем из текущего состояния стора тот запрос, id которого мы передали в acceptFriendRequestEffect .

UI:

Ну и это просто рендерится и вызывается в UI например так:

https://github.com/VanyaMate/product/blob/master/src/features/friend/button/CompositeAddFriendButton/ui/CompositeAddFriendButton.tsx
https://github.com/VanyaMate/product/blob/master/src/features/friend/button/AcceptFriendRequestButton/ui/AcceptFriendRequestButton.tsx

// CompositeAddFriendButton
  
const receivedFriendRequest: DomainFriendRequest = requestsReceived.find((request) => request.user.id === userId);
if (receivedFriendRequest) {
    return (
        <AcceptFriendRequestButton
            requestId={ receivedFriendRequest.requestId }
        />
    );
}

// AcceptFriendRequestButton

const { requestId, ...other } = props;

return (
    <ButtonWithLoading
        { ...other }
        onClick={ () => acceptFriendRequestEffect(requestId) }
        quad
        styleType={ ButtonStyleType.SECOND }
    >
        <IoPersonAdd/>
    </ButtonWithLoading>
);

-----------------------

Так же мы можем создать другой эффект, с другим экшеном


export const acceptFriendRequestNotificationEffect = effect(acceptFriendRequestNotificationAction);
// ... Так же подписаться
.on(acceptFriendRequestNotificationEffect, 'onSuccess', (state, { result }) => state.filter((request) => request.requestId !== result.requestId))

А потом вызывать его и всё будет так же работать

https://github.com/VanyaMate/product/blob/master/src/features/notification/hooks/useFriendsStoreUpdaterByNotifications.ts

const onFriendRequestAcceptedIn: NotificationNotificatorCallback = (notifications) => {
    notifications.forEach(({ data }) => acceptFriendRequestNotificationEffect(data));
};

Зумеры изобрели MVC

Здравствуйте.

MVC - это совсем другое. Это скорее MVI.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации