dots классный, но пока что в нем не хватает анимаций. Да, есть всякие готовые инструменты из asset store для шейдерных анимаций, но они и стоят.. хотя я не пробовал сделать свой такой.. может это и не так сложно..
Вроде как в этом году должен выйти от самих unity новый инструмент для работы с анимациями с поддержкой ecs. На какой-то конференции уже показывали демку.
Не уверен, но могу предположить, что это для того, чтобы возвращать пользователю страницу сразу с той темой, которая у него выставлена, а не выяснять это на клиенте после загрузки страницы.
Вернув сразу только необходимую тему со всеми настройками страницы, нам не нужно будет загружать их все. Так же у нас не будет мигания тем при загрузке страницы.
Мне помогла временная смена направления. С фронта на пол годика в геймдев окунулся) Много всего нового, было интересно) Потом обратно вернулся, но уже с полной сменой стека инструментов.. Тоже.. много нового, новое подходы.. Сейчас иногда просто беру и на недельку окунаюсь в какую нибудь технологию.. мобильную разработку.. еще во что угодно..
Как мне кажется у меня это было из-за однообразности того что я делал, а как следствие - "я всё знаю". Я учился делать хорошо то, что я делал, и, можно сказать, развивался в "ширину" в той области где я работал, но не учился в "длину" и не узнавал (почти) никаких других подходов, технологий итд. Это точно была ошибка.
Если вы, например, пишите фронт на реакте.. попробуйте flutter и мобильную разработку.. просто попробуйте.. на недельку залетите, посмотрите что это такое.. Или, банально, арнуляр.. или на чистом js(ts)..
Или пишите вы бекенд на Java.. с определенными технологиями.. паттернами.. А попробуйте PHP и Laravel.. на недельку)
Или попробуйте полностью абстрагироваться от своих знаний и просто придумать что-то заново. Например вы для себя решили, что "такой-то подход" самый правильный. А попробуйте придумать что-то лучше.. Что-то проще.. Что-то удобнее.. А потом привнести это в то место где вы работаете.
В конечном итоге помогло именно сменить технологии и узнать очень много новых направлений в которых я могу развиваться и ушло это чувство, что "я всё знаю".
А в чём конкретно запутанность и сложность того примера, что вам скинули выше?
Если мы берем именно пример который скинули - сложности никакой. Сложность возникнет при росте приложения.
А чем он сильно сложнее? EventEmitter и вообще событийная модель это де факто стандарт в JS с лохматых времен, он что-ли сложный и запутанный?....
Сильно сложнее чем тот что показывал я. Он всё еще не сложный. Смотрите пункт 1.
А разве там не было ответа на этот тривиальный вопрос?)
Я каким то образом не заметил и вообще впервые вижу эти скриншоты в комменатрии.. Возможно они просто у меня не прогрузились.. Так что да, это то что я и имел ввиду.
Лишне? Я так понимаю сам факт написания кода выглядит как что-то лишнее..
Писать отдельно функцию, а потом в каком то классе делать метод только для того, чтобы вызвать только эту функцию - выглядит лишним, да. Моё мнение.
Сложной?
"но тогда получится то что писал я, но с более сложной реализацией..".
С более сложной реализацией. Не сложной. Более сложной.
А начиная с какого года это стало сложным?
Это не сложно) Если вы прочтете комментарий, то увидите там : " Я могу представить варианты когда такой подход можно реализовать так как написал я, я могу представить варианты при которых EventEmitter подойдет лучше ".
А вот это легко? Чем отличается от того, что выше?
Если вы понимаете, что то как вы делаете - лучший для вас вариант, то используйте его. Я предложил лишь новый инструмент, который, разумеется, подойдет не в каждом случае и не для каждого.
Для себя я решил, что вариант предложенный в статье гораздо удобнее и гибче. С ним я стал видеть всё приложение целиком. Ушла какая либо запутанность.
Плюс статья то не про реализацию.. а про архитектуру..
----
Но даже если посмотреть на новый код который вы скинули, он уже сильно сложнее и больше чем тот что показывал я..
Плюс если вы захотите использовать fetchPosts в каком-нибудь новом месте - вам туда нужно будет подгружать весь PostsState, а, возможно, вы этого не захотите.. То есть вы уже, для того чтобы в любом месте вызвать метод fetchPosts , вместе с ним так же получите и все остальные методы и поля которые там есть.
Да.. вы можете его вынести отдельно.. и вызывать его в методе fetchPosts внутри класса.. Опять же.. это даже выглядит как что-то лишнее..
Да.. вы можете всё обмазать эвентами.. но тогда получится то что писал я, но с более сложной реализацией..
----
Если взять ваш последний код, вынести fetchData, logout и createNewPost в отдельные функции, и добавить внутрь них .emit -теры, а внутри моделей подписываться на вызовы этих эмиттеров, то, по сути, получится то что писал я, но.. ваши функции будут связаны с эмиттером.
Тогда мы можем сделать над ними обертку, да. И тогда вы получаете effect .
И вот вы сделали то что писал я, но с более сложной реализацией.
И теперь мы получаем самодостаточные функции, которые можно вызывать откуда и когда угодно, без подтягивания даже mobx-а в банлд.
В моделях остается только работа с данными без зависимости от чего-то внешнего (кроме EventEmitter в вашем случае), благодаря чему их легко можно тестировать без всяких там mock -ов.
----
Повторюсь.. Я не предлагаю что-то на все случаи жизни, что подойдет вам в любой ситуации для любого приложения. Я лишь предложил еще один вариант.
Я могу представить варианты когда такой подход можно реализовать так как написал я, я могу представить варианты при которых EventEmitter подойдет лучше, я могу представить варианты при которых ни один из этих вариантов не подойдет и нужно писать что-то под конкретную задачу с максимальной производительностью.
Если вы понимаете, что тот подход который вы используете лучше, вы прям осознанно понимаете в чем конкретно и в каких ситуациях (или во всех) - то используйте его. Предлагаю написать о нем статью и поделиться со всеми, возможно, я просто не понимаю всей прелести вашего подхода и то как я писал раньше ничего общего с тем, как пишете вы - не имеет.
Можно спокойно и на редаксе с редюсерами написать всё приложение, и на зуснанде, и на реактовском контексте, и на чем угодно другом и с любым подходом. Но я поделился тем, что прям сильно, лично для меня, облегчило разработку.
Да, я полностью понимаю о чем вы, я сам раньше делал так же.
Но а что если вы захотите использовать эту функцию загрузки постов еще где-то, но без того, чтобы что-то обновлялось?
Нужно её выносить в отдельный дополнительный метод или функцию (файл).
А что если вы захотите при logout очищать массив постов?
Вам нужно будет из некого class Auth из метода logout дергать PostsState.clear()? А потом не только посты, а еще десятки других классов и методов?
Всё в итоге это становится более запутанным..
Я полностью понимаю о чем вы говорите, повторюсь, я пол года назад писал так как вы сейчас говорите и считал это классным решением, хоть с ним всё равно были небольшие трудности.
Если вы считаете этот подход самым удобным для вас - пожалуйста, вам никто не запрещает его использовать. Я лишь делюсь своими мыслями и подходами, которые, лично для меня, оказались гораздо удобнее и проще.
И теперь я могу использовать getPosts - где угодно и когда угодно.
Если мне нужно подгрузить посты - я просто вызываю getPostsEffect из любого места в любое время и всё.
Если мне нужно очищать postsList при logout я не лезу в какие-то другие классы и ничего там не меняю/дописываю, я иду именно к postsList и дописываю подписку на logoutEffect .
Если прочесть статью, то там я как раз таки это и пишу, что Redux и Effector не нужны, потому что они дают слишком много чем мне нужно и самый лучший вариант писать что-то своё.
Тот пример который скинули вы - примерно так я делал чуть больше чем пол года назад, да, я понимаю о чем вы, я много так писал, но он гораздо сложнее, лично для меня.
Казалось бы, мы просто создаем классы с данными, методы для работы и всё круто. Никаких хуков для работы с этим не нужно, просто экспортируем в компоненты и радуемся жизни.
makeAutoObservable, observer и всё работает.
Я достаточно долго думал что бы я мог сказать самому себе прошлому который считал точно так же как и вы сейчас, чтобы объяснить то что я имею ввиду, но не могу придумать ничего такого, что переубедило бы лично меня, по этому даже пытаться не буду сравнивать эти два подхода и рассказывать про минусы того, о чем описали вы.
Я бы просто попросил, если вам это интересно, потратить парочку часов и реализовать простенькое любое приложение с полностью MVA подходом.
Да даже тот пример который скинули вы просто переписать, но лучше что-то более реальное.
Создаете отдельно Action. Например получение списка постов. Это просто запрос на сервер, валидация данных их возвращение.
Создаете парочку Model. postsIsPending и postsList, например.
Создаете Effectобертку над Action, подписываетесь на неё моделями и всё.
Дальше просто рендерите эти данные.
----
Пожалуйста, если вы точно знаете, что - то о чем говорите вы - это то что для вас гораздо удобнее, пожалуйста, это ваше право делать так как вы хотите.
Я просто делюсь своими идеями.
Я да, я так же как и с Redux, Effector, считаю что и MobX не нужен) Просто на нем вроде как нельзя реализовать "просто" то о чем писал я, по этому я о нем и не писал.
На счет MVVM. Как я понимаю - из похожего это то, что View подписывается на изменения предоставляемые ViewModel. Но дальше начинаются отличия такие, что я не могу вообще назвать их похожими.
Реализация на MVVM будет похоже на реализацию через MobX, где мы создаем класс, подписываемся на его изменения из View, даем ему методы которые эта же View дергает.
Да, мы так же можем вынести Action отдельно, да, в каком-то смысле мы так же вызываем только действия, а дальше оно всё само происходит, но из-за отсутствия эффектов нам придется вручную вызывать некоторые Action из других Action, потому что MVVM не предполагает, как я понимаю, подписок на них. Хотя мы конечно можем создать что-то похоже.. опять же как посмотреть.. но.. это прям гораздо сложнее..
Суть подхода заключается в том, чтобы View просто подписалась на Model, а Model на Effect. Мы дергаем Effect из любой точки приложения - Model сама себя обновляет (мы не заботимся об этом), View сама себя обновляет (мы не заботимся об этом).
В итоге из любого участка приложения будь то UI, уведомления, какое-то броадкаст сообщение, что угодно, мы вызываем действие getPosts - наша Model сама по подписке ставит isLoading = true, View рендерит <Loader/>. Действие закончилось успешно - наша Model сама по подписке ставит isLoading = false, posts = result, View рендерит список постов.
А всё что мы сделали - просто вызвали какой-то Effect.
Хороший пример можно привести с logoutEffect из "Социальной сети".
logoutEffect это обертка над logout запросом на сервер и очисткой localStorage.
у меня есть много разных моделей в приложении и при logout я бы хотел их все очищать.
я просто всеми ими подписываютсь на этот logoutEffect и они все очищаются.
Во Vue это точно можно сделать. К сожалению я не знаю Vue, но вы точно так же как и во всех примерах создаете Action и Model. Их можно буквально просто скопировать в проект и всё будет работать так же. А дальше, тут я уже не знаю как, потому что это особенности Vue, подписываетесь на изменения стора и точно так же из любого участка кода вызываете Effect-ы. У Effector есть пакет effector-vue . Вот как-то так. Но, думаю, как и в React мы можем просто создать такой же хук для работы и с самописным стором.
С Angular всё сложно, потому что сделать так же просто как в React, Svelte, Solid, Vue - не выйдет. Там другая вообще архитектура приложений. Опять же, Angular я не знаю на таком уровне, чтобы прям точно дать ответ как это там можно сделать, но думаю как-то можно, но не факт, что это будет удобнее чем какие-то другие варианты которые используются и реализуются в Angular. Всё таки MVA это тоже инструмент и он не всегда подходит.
input.current исправил, спасибо. Про event, да, понимаю, что есть разные способы того как это можно сделать, но это всё не относится к теме того о чем я пытался рассказать и я хотел просто привести максимально простой пример на самых простых (с точки зрения визуального восприятия кода) технологиях. Тут можно было хоть псевдокодить, тема была в другом.
Есть store $friendRequestsReceived (список полученных запросов в друзья) И при успешном завершении эффекта acceptFriendRequestEffect мы удаляем из текущего состояния стора тот запрос, id которого мы передали в acceptFriendRequestEffect .
UI:
Ну и это просто рендерится и вызывается в UI например так:
Экшенов будет столько сколько вам нужно и не больше. Они бы и так были, просто, возможно, как часть какого-то другого кода.. Пока что, к сожалению, не могу привести пример большого проекта с полностью только таким походом, но позже дам ссылку на пример, когда он появится.
Я говорил о другом и этот подход никак с этими проблемами не связан, а писал я про взаимодействие 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 -> модель обновляет себя из-за подписки -> UI рендерит.
------------
И все эти задачи сами по себе не связаны с тем что я описывал в статье. К сожалению, видимо, плохо её написал и не очень много людей поняло что я имел ввиду вообще, судя по комментариям.
Тут не про эффектор, не про реакт, не про то как вам реализовать это всё. Тут принципы и абстракция. Всё.
Я вообще свой стор написал из 3-х функций store, effect и сombine . Больше и не нужно.
UI - рендерит Model - сама себя обновляет Action - просто выполняют какую то задачу
UI - не меняет стор, не вызывает редюсеры, а просто рендерит и вызывает action-ы (через эффекты). Эффекты это связь экшена и модели.
Почему я показал на react? Просто он самый популярный и очень понятный код.
Почему эффектор? Не такой популярный, но очень понятный код.
Почему плохо работающая тудушка? Очень простой пример.
------------
Просто попробуйте представить себе любой атомарный компонент (widget).
Вся его задача сводится к рендеру данных и вызову какого либо эффекта. Всё.
Вызываем действия, которые просто что-то делают. Например создают новую задачу. Как это будет сделано - не важно. Локал сторейдж или fetch или комбинация этих действий.. Мы просто подписываемся на начало этого действия и конец, с ожиданием того, что нам вернется новая задача которую модель сама в себя вставит, а UI получит автоматически новые данные и отрендерит их. Не важно где был вызван этот эффект. Через UI, из пуш уведомления, из какого-то евента.. Он вызвался - по подписке обновились данные - рендер. Всё. Это вся идея. Удобно. Понятно. Просто.
Это не реклама эффектора) И я не говорю, что используйте его) Причина почему примеры на нем - они простые и понятные. Я лишь упомянул, что с его помощью это легко реализуется. Есть другие инструменты.
Всё что мне нужно от эффектора: createStore, createEffect. Всё. Для реакта еще из effector-react импортировать хук useUnit.
Но спасибо за видос, гляну) Полезно, думаю, будет)
1. Я не знаю эффектор так хорошо. В первую очередь я взял его потому что на нем можно легко и быстро реализовать то что я имел ввиду, и я взял только тот функционал который мне необходим (createStore, createEffect), а для этого "pending" не нужен. 2. То как эта идея будет реализована - не важно, это уже относится к реализации, а я предлагаю абстракцию, поток данных. Так же отслеживание конкретного эффекта выбивается из идеи. 3. Подписываться на конкретный эффект, в каких то случаях, действительно может быть лучше. В том же редаксе, можно сделать dispatch.unwrap и получить промис, состояние которого можно отслеживать. Но, допустим я решил так же делать todoAdding в true когда у меня вызывается эффект getTodos. Тогда мне нужно будет идти в тот(те) компонент(ы) где я отслеживаю этот effect и менять там что-то.. А это уже именно то от чего я и хочу полностью избавиться.. С todoAdding плохой пример, потому что это буквально стор для добавления новой задачи, и, скорее всего, у нас будет только один такой эффект который будет добавлять задачу, тут с вами согласен.
Я пытался создать максимально простые примеры содержащие только суть моей идеи. UI просто рендерит данные и вызывает экшены. Без отслеживания состояния этих экшенов и всего такого. (да, для оптимизации иногда будет лучше отслеживать экшены которые вызываются, но это достаточно редкие случаи, от которых лучше отказаться, если есть возможность)
Да, согласен) Кеширование по мере надобности) Плюс, это в какой то степени, будет ограничивающим фактором использования хука. Ведь мы, возможно, хотим получать актуальные данные при запросе на один и тот же url, которые будут (или могут быть) всегда разными, из-за чего кеширование нам не подходит. В общем) да)
Мне кажется, что еще можно сделать такие улучшения.
1. Изначально задать isLoading в true. Ну или сделать проверку, что если url есть, то true, иначе false, но это если мы допускаем, что url может быть пустым. В таком случае мы избавимся от одного лишнего ререндера когда isLoading, при первом вызове, будет становится в true. Но тогда еще и проверку внутри useEffect можно делать, чтобы не делать лишних действий если url не правильный/его в принципе не указали.
2. Я делал еще так, что в цепочке промисов - я не сразу обновляю стейт, а сохраняю в переменные внутри useEffect, чтобы избежать лишних рендеров. А в конце в finally устанавливаю всё сразу. В конкретно этом примере, опять же, это не нужно, но в более сложных - может понадобиться. Например если мы каким-то асинхронным валидатором решим проверить наши данные о.О ну условно) Или банально делаем несколько последовательных запросов или асинхронных операций)
function useFetch(url) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// флаг отмены
let cancelled = false;
let data = null;
let error = null;
setIsLoading(true);
setData(null);
setError(null);
fetch(url)
.then((res) => res.json())
.then((respData) => {
if (!cancelled) data = respData;
})
.catch((e) => {
if (!cancelled) error = e;
})
.finally(() => {
if (!cancelled) {
setData(data);
setError(error);
setIsLoading(false);
}
});
return () => {
// выставим признак того, что запрос отменен
cancelled = true;
};
}, [url]);
return [data, isLoading, error];
}
Это что касается именно реакта.
Так же - конечно, лучше, как уже писали, и мне так тоже кажется, использовать AbortController. Так же - я бы использовал не массив для возвращения, а объект, чтобы брать только то что нужно. Так же - кеширование fetch, или, например, хранить кеши внутри хука.. если нужно. В общем от задачи хука много зависит) Думаю так.
Ахаха) Не, изначально было "React. Чистые и грязные компоненты", но потом я подумал, что это можно использовать не только с React и поменял на Frontend)
На начало 2025:
dots классный, но пока что в нем не хватает анимаций. Да, есть всякие готовые инструменты из asset store для шейдерных анимаций, но они и стоят.. хотя я не пробовал сделать свой такой.. может это и не так сложно..
Вроде как в этом году должен выйти от самих unity новый инструмент для работы с анимациями с поддержкой ecs. На какой-то конференции уже показывали демку.
Не уверен, но могу предположить, что это для того, чтобы возвращать пользователю страницу сразу с той темой, которая у него выставлена, а не выяснять это на клиенте после загрузки страницы.
Вернув сразу только необходимую тему со всеми настройками страницы, нам не нужно будет загружать их все. Так же у нас не будет мигания тем при загрузке страницы.
Мне помогла временная смена направления. С фронта на пол годика в геймдев окунулся) Много всего нового, было интересно) Потом обратно вернулся, но уже с полной сменой стека инструментов.. Тоже.. много нового, новое подходы.. Сейчас иногда просто беру и на недельку окунаюсь в какую нибудь технологию.. мобильную разработку.. еще во что угодно..
Как мне кажется у меня это было из-за однообразности того что я делал, а как следствие - "я всё знаю". Я учился делать хорошо то, что я делал, и, можно сказать, развивался в "ширину" в той области где я работал, но не учился в "длину" и не узнавал (почти) никаких других подходов, технологий итд. Это точно была ошибка.
Если вы, например, пишите фронт на реакте.. попробуйте flutter и мобильную разработку.. просто попробуйте.. на недельку залетите, посмотрите что это такое.. Или, банально, арнуляр.. или на чистом js(ts)..
Или пишите вы бекенд на Java.. с определенными технологиями.. паттернами.. А попробуйте PHP и Laravel.. на недельку)
Или попробуйте полностью абстрагироваться от своих знаний и просто придумать что-то заново. Например вы для себя решили, что "такой-то подход" самый правильный. А попробуйте придумать что-то лучше.. Что-то проще.. Что-то удобнее.. А потом привнести это в то место где вы работаете.
В конечном итоге помогло именно сменить технологии и узнать очень много новых направлений в которых я могу развиваться и ушло это чувство, что "я всё знаю".
А в чём конкретно запутанность и сложность того примера, что вам скинули выше?
Если мы берем именно пример который скинули - сложности никакой. Сложность возникнет при росте приложения.
А чем он сильно сложнее? EventEmitter и вообще событийная модель это де факто стандарт в JS с лохматых времен, он что-ли сложный и запутанный?....
Сильно сложнее чем тот что показывал я. Он всё еще не сложный. Смотрите пункт 1.
А разве там не было ответа на этот тривиальный вопрос?)
Я каким то образом не заметил и вообще впервые вижу эти скриншоты в комменатрии.. Возможно они просто у меня не прогрузились.. Так что да, это то что я и имел ввиду.
Лишне? Я так понимаю сам факт написания кода выглядит как что-то лишнее..
Писать отдельно функцию, а потом в каком то классе делать метод только для того, чтобы вызвать только эту функцию - выглядит лишним, да. Моё мнение.
Сложной?
"но тогда получится то что писал я, но с более сложной реализацией..".
С более сложной реализацией. Не сложной. Более сложной.
А начиная с какого года это стало сложным?
Это не сложно) Если вы прочтете комментарий, то увидите там :
" Я могу представить варианты когда такой подход можно реализовать так как написал я, я могу представить варианты при которых
EventEmitter
подойдет лучше ".А вот это легко? Чем отличается от того, что выше?
Всем.
Так я уже ведь всё написал, что хотел..
Если вы понимаете, что то как вы делаете - лучший для вас вариант, то используйте его. Я предложил лишь новый инструмент, который, разумеется, подойдет не в каждом случае и не для каждого.
Для себя я решил, что вариант предложенный в статье гораздо удобнее и гибче. С ним я стал видеть всё приложение целиком. Ушла какая либо запутанность.
Плюс статья то не про реализацию.. а про архитектуру..
----
Но даже если посмотреть на новый код который вы скинули, он уже сильно сложнее и больше чем тот что показывал я..
Плюс если вы захотите использовать
fetchPosts
в каком-нибудь новом месте - вам туда нужно будет подгружать весьPostsState
, а, возможно, вы этого не захотите.. То есть вы уже, для того чтобы в любом месте вызвать методfetchPosts
, вместе с ним так же получите и все остальные методы и поля которые там есть.Да.. вы можете его вынести отдельно.. и вызывать его в методе
fetchPosts
внутри класса.. Опять же.. это даже выглядит как что-то лишнее..Да.. вы можете всё обмазать эвентами.. но тогда получится то что писал я, но с более сложной реализацией..
----
Если взять ваш последний код, вынести
fetchData
,logout
иcreateNewPost
в отдельные функции, и добавить внутрь них.emit
-теры, а внутри моделей подписываться на вызовы этих эмиттеров, то, по сути, получится то что писал я, но.. ваши функции будут связаны с эмиттером.Тогда мы можем сделать над ними обертку, да. И тогда вы получаете
effect
.И вот вы сделали то что писал я, но с более сложной реализацией.
И теперь мы получаем самодостаточные функции, которые можно вызывать откуда и когда угодно, без подтягивания даже mobx-а в банлд.
В моделях остается только работа с данными без зависимости от чего-то внешнего (кроме
EventEmitter
в вашем случае), благодаря чему их легко можно тестировать без всяких тамmock
-ов.----
Повторюсь.. Я не предлагаю что-то на все случаи жизни, что подойдет вам в любой ситуации для любого приложения. Я лишь предложил еще один вариант.
Я могу представить варианты когда такой подход можно реализовать так как написал я, я могу представить варианты при которых
EventEmitter
подойдет лучше, я могу представить варианты при которых ни один из этих вариантов не подойдет и нужно писать что-то под конкретную задачу с максимальной производительностью.Если вы понимаете, что тот подход который вы используете лучше, вы прям осознанно понимаете в чем конкретно и в каких ситуациях (или во всех) - то используйте его. Предлагаю написать о нем статью и поделиться со всеми, возможно, я просто не понимаю всей прелести вашего подхода и то как я писал раньше ничего общего с тем, как пишете вы - не имеет.
Можно спокойно и на редаксе с редюсерами написать всё приложение, и на зуснанде, и на реактовском контексте, и на чем угодно другом и с любым подходом. Но я поделился тем, что прям сильно, лично для меня, облегчило разработку.
Да, я полностью понимаю о чем вы, я сам раньше делал так же.
Но а что если вы захотите использовать эту функцию загрузки постов еще где-то, но без того, чтобы что-то обновлялось?
Нужно её выносить в отдельный дополнительный метод или функцию (файл).
А что если вы захотите при
logout
очищать массив постов?Вам нужно будет из некого
class Auth
из методаlogout
дергатьPostsState.clear()
? А потом не только посты, а еще десятки других классов и методов?Всё в итоге это становится более запутанным..
Я полностью понимаю о чем вы говорите, повторюсь, я пол года назад писал так как вы сейчас говорите и считал это классным решением, хоть с ним всё равно были небольшие трудности.
Если вы считаете этот подход самым удобным для вас - пожалуйста, вам никто не запрещает его использовать. Я лишь делюсь своими мыслями и подходами, которые, лично для меня, оказались гораздо удобнее и проще.
****
Тут конечно упрощенный код, но это не важно.
Вы сделали так как сделали, а можно сделать так:
И теперь я могу использовать
getPosts
- где угодно и когда угодно.Если мне нужно подгрузить посты - я просто вызываю
getPostsEffect
из любого места в любое время и всё.Если мне нужно очищать
postsList
приlogout
я не лезу в какие-то другие классы и ничего там не меняю/дописываю, я иду именно кpostsList
и дописываю подписку наlogoutEffect
.И вот они все случаи когда обновляется
postsList
.А теперь давайте еще представим, что у нас появились уведомления.
К нам пришло уведомление о новом посте и мы хотим добавлять его в список постов.
Ну и так далее.
Я не лезу никуда для того чтобы откуда то обновлять
postsList
. Всё вон оно, в одном месте.Здравствуйте.
Архитектура не диктует то с помощью каких инструментов вам нужно реализовать её.
Архитектура закладывает принципы, структуру, но как вы это реализуете - это уже решаете вы.
Если вы хотите использовать ООП, применять принципы SOLID для реализации, пожалуйста, вы это можете сделать.
Здравствуйте.
Если прочесть статью, то там я как раз таки это и пишу, что Redux и Effector не нужны, потому что они дают слишком много чем мне нужно и самый лучший вариант писать что-то своё.
Тот пример который скинули вы - примерно так я делал чуть больше чем пол года назад, да, я понимаю о чем вы, я много так писал, но он гораздо сложнее, лично для меня.
Казалось бы, мы просто создаем классы с данными, методы для работы и всё круто. Никаких хуков для работы с этим не нужно, просто экспортируем в компоненты и радуемся жизни.
makeAutoObservable, observer и всё работает.
Я достаточно долго думал что бы я мог сказать самому себе прошлому который считал точно так же как и вы сейчас, чтобы объяснить то что я имею ввиду, но не могу придумать ничего такого, что переубедило бы лично меня, по этому даже пытаться не буду сравнивать эти два подхода и рассказывать про минусы того, о чем описали вы.
Я бы просто попросил, если вам это интересно, потратить парочку часов и реализовать простенькое любое приложение с полностью MVA подходом.
Да даже тот пример который скинули вы просто переписать, но лучше что-то более реальное.
Создаете отдельно Action. Например получение списка постов. Это просто запрос на сервер, валидация данных их возвращение.
Создаете парочку Model. postsIsPending и postsList, например.
Создаете Effect обертку над Action, подписываетесь на неё моделями и всё.
Дальше просто рендерите эти данные.
----
Пожалуйста, если вы точно знаете, что - то о чем говорите вы - это то что для вас гораздо удобнее, пожалуйста, это ваше право делать так как вы хотите.
Я просто делюсь своими идеями.
Я да, я так же как и с Redux, Effector, считаю что и MobX не нужен) Просто на нем вроде как нельзя реализовать "просто" то о чем писал я, по этому я о нем и не писал.
Здравствуйте.
На счет MVVM. Как я понимаю - из похожего это то, что View подписывается на изменения предоставляемые ViewModel. Но дальше начинаются отличия такие, что я не могу вообще назвать их похожими.
Реализация на MVVM будет похоже на реализацию через MobX, где мы создаем класс, подписываемся на его изменения из View, даем ему методы которые эта же View дергает.
Да, мы так же можем вынести Action отдельно, да, в каком-то смысле мы так же вызываем только действия, а дальше оно всё само происходит, но из-за отсутствия эффектов нам придется вручную вызывать некоторые Action из других Action, потому что MVVM не предполагает, как я понимаю, подписок на них. Хотя мы конечно можем создать что-то похоже.. опять же как посмотреть.. но.. это прям гораздо сложнее..
Суть подхода заключается в том, чтобы View просто подписалась на Model, а Model на Effect. Мы дергаем Effect из любой точки приложения - Model сама себя обновляет (мы не заботимся об этом), View сама себя обновляет (мы не заботимся об этом).
В итоге из любого участка приложения будь то UI, уведомления, какое-то броадкаст сообщение, что угодно, мы вызываем действие getPosts - наша Model сама по подписке ставит isLoading = true, View рендерит <Loader/>. Действие закончилось успешно - наша Model сама по подписке ставит isLoading = false, posts = result, View рендерит список постов.
А всё что мы сделали - просто вызвали какой-то Effect.
Хороший пример можно привести с logoutEffect из "Социальной сети".
logoutEffect это обертка над logout запросом на сервер и очисткой localStorage.
у меня есть много разных моделей в приложении и при logout я бы хотел их все очищать.
я просто всеми ими подписываютсь на этот logoutEffect и они все очищаются.
Вот вам один пример
----
Теперь про Vue и Angular.
Во Vue это точно можно сделать. К сожалению я не знаю Vue, но вы точно так же как и во всех примерах создаете Action и Model. Их можно буквально просто скопировать в проект и всё будет работать так же. А дальше, тут я уже не знаю как, потому что это особенности Vue, подписываетесь на изменения стора и точно так же из любого участка кода вызываете Effect-ы. У Effector есть пакет
effector-vue
. Вот как-то так. Но, думаю, как и в React мы можем просто создать такой же хук для работы и с самописным стором.С Angular всё сложно, потому что сделать так же просто как в React, Svelte, Solid, Vue - не выйдет. Там другая вообще архитектура приложений. Опять же, Angular я не знаю на таком уровне, чтобы прям точно дать ответ как это там можно сделать, но думаю как-то можно, но не факт, что это будет удобнее чем какие-то другие варианты которые используются и реализуются в Angular. Всё таки MVA это тоже инструмент и он не всегда подходит.
Здравствуйте.
Да, всё так.
input.current
исправил, спасибо.Про event, да, понимаю, что есть разные способы того как это можно сделать, но это всё не относится к теме того о чем я пытался рассказать и я хотел просто привести максимально простой пример на самых простых (с точки зрения визуального восприятия кода) технологиях. Тут можно было хоть псевдокодить, тема была в другом.
Здравствуйте.
Я переписал проект с 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
Давайте разберем один пример:
Мы создаем эффект основанный на
action
. Мы получаем ту же самую функцию, с той же сигнатурой, но теперь мы подписаться на неё можем.Создаем список полученных запросов в друзья.
Подписываем
store
на этотeffect
и готовоЕсть 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
-----------------------
Так же мы можем создать другой эффект, с другим экшеном
А потом вызывать его и всё будет так же работать
https://github.com/VanyaMate/product/blob/master/src/features/notification/hooks/useFriendsStoreUpdaterByNotifications.ts
Экшенов будет столько сколько вам нужно и не больше. Они бы и так были, просто, возможно, как часть какого-то другого кода.. Пока что, к сожалению, не могу привести пример большого проекта с полностью только таким походом, но позже дам ссылку на пример, когда он появится.
Здравствуйте.
MVC - это совсем другое. Это скорее MVI.
Здравствуйте.
Я говорил о другом и этот подход никак с этими проблемами не связан, а писал я про взаимодействие UI, Model, Action. Всё. То есть то есть UI просто рендерит и вызывает эффекты. А модель сама себя обновляет подписками на эффекты.
Я просто предложил некую абстракцию на этом уровне и не дальше. Как это будет реализовано - это уже не важно, главное - принципы.
Но давайте рассмотрим все проблемы которые вы описали в рамках самого простого примера (плохо работающей тудушки).
Я скажу больше. Тут об этом и других задачах - даже ни слова. Так что же с этим делать? Да куча есть всего. Как вы правильно написали - стореджи, воркеры, броадкасты, постпесседжи и что угодно.
Давайте представим, что у нас есть только хранилище статуса добавления задачи.
И в одной из вкладок мы начали добавлять задачу, пошел процесс. Другие вкладки про это ничего не знают, согласен.
Мы берем и из вкладки которая начала процесс - отправляем через, например, броадкаст это уведомление.
Другие вкладки получают это сообщение, понимают что это такое и выполняют какой-то свой экшен, на который подписана модель. И обновляет свое состояние.
Как мы свяжем первичный экшен с отправкой уведомления - не важно. Мы для это можем например написать свой интерфейс или сделать обертку над экшеном или над эффектом итд. Опять же, это уже детали реализации.
Но в момент вызова effect-а у нас будет отправляться postMessage всем слушателям с типом сообщения и данными.
На другом конце провода (в другой вкладке) ловится это сообщение, берется тип сообщения и вызывается соответсвующий effect с этим payload. На этот экшен была подписана модель - модель обновляется.
Вот у нас есть общение между вкладками которое соответствует тому что я писал. Модель сама себя обновляет подпиской на эффекты. А UI просто отрендерит новые данные.
------------
Ну тогда заменяем броадкаст на локалсторейдж и вот у нас есть локальное хранилище.
Разделяем экшены на 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, из пуш уведомления, из какого-то евента.. Он вызвался - по подписке обновились данные - рендер. Всё. Это вся идея. Удобно. Понятно. Просто.
Здравствуйте.
Это не реклама эффектора) И я не говорю, что используйте его) Причина почему примеры на нем - они простые и понятные. Я лишь упомянул, что с его помощью это легко реализуется. Есть другие инструменты.
Всё что мне нужно от эффектора: createStore, createEffect. Всё. Для реакта еще из effector-react импортировать хук useUnit.
Но спасибо за видос, гляну) Полезно, думаю, будет)
Здравствуйте.
1. Я не знаю эффектор так хорошо. В первую очередь я взял его потому что на нем можно легко и быстро реализовать то что я имел ввиду, и я взял только тот функционал который мне необходим (createStore, createEffect), а для этого "pending" не нужен.
2. То как эта идея будет реализована - не важно, это уже относится к реализации, а я предлагаю абстракцию, поток данных. Так же отслеживание конкретного эффекта выбивается из идеи.
3. Подписываться на конкретный эффект, в каких то случаях, действительно может быть лучше. В том же редаксе, можно сделать dispatch.unwrap и получить промис, состояние которого можно отслеживать. Но, допустим я решил так же делать todoAdding в true когда у меня вызывается эффект getTodos. Тогда мне нужно будет идти в тот(те) компонент(ы) где я отслеживаю этот effect и менять там что-то.. А это уже именно то от чего я и хочу полностью избавиться..
С todoAdding плохой пример, потому что это буквально стор для добавления новой задачи, и, скорее всего, у нас будет только один такой эффект который будет добавлять задачу, тут с вами согласен.
Я пытался создать максимально простые примеры содержащие только суть моей идеи. UI просто рендерит данные и вызывает экшены. Без отслеживания состояния этих экшенов и всего такого. (да, для оптимизации иногда будет лучше отслеживать экшены которые вызываются, но это достаточно редкие случаи, от которых лучше отказаться, если есть возможность)
Да, согласен) Кеширование по мере надобности) Плюс, это в какой то степени, будет ограничивающим фактором использования хука. Ведь мы, возможно, хотим получать актуальные данные при запросе на один и тот же url, которые будут (или могут быть) всегда разными, из-за чего кеширование нам не подходит. В общем) да)
Здравствуйте)
Мне кажется, что еще можно сделать такие улучшения.
1. Изначально задать isLoading в true. Ну или сделать проверку, что если url есть, то true, иначе false, но это если мы допускаем, что url может быть пустым.
В таком случае мы избавимся от одного лишнего ререндера когда isLoading, при первом вызове, будет становится в true. Но тогда еще и проверку внутри useEffect можно делать, чтобы не делать лишних действий если url не правильный/его в принципе не указали.
2. Я делал еще так, что в цепочке промисов - я не сразу обновляю стейт, а сохраняю в переменные внутри useEffect, чтобы избежать лишних рендеров. А в конце в finally устанавливаю всё сразу. В конкретно этом примере, опять же, это не нужно, но в более сложных - может понадобиться. Например если мы каким-то асинхронным валидатором решим проверить наши данные о.О ну условно) Или банально делаем несколько последовательных запросов или асинхронных операций)
Это что касается именно реакта.
Так же - конечно, лучше, как уже писали, и мне так тоже кажется, использовать AbortController.
Так же - я бы использовал не массив для возвращения, а объект, чтобы брать только то что нужно.
Так же - кеширование fetch, или, например, хранить кеши внутри хука.. если нужно.
В общем от задачи хука много зависит) Думаю так.
Ахаха) Не, изначально было "React. Чистые и грязные компоненты", но потом я подумал, что это можно использовать не только с React и поменял на Frontend)
Спасибо)
На счет книг особенно согласен. Только недавно их начал читать и почти сразу понял, что было большой ошибкой не начать читать их раньше)