Pull to refresh

Comments 15

Интересный подход, но как будто бы он повторяет основной принцип MVVM (Model-View-ViewModel). Поправьте если это не так)

Но задумка в целом интересная, но я не могу ее представить в других фреймворках, том же Vue или Angular. Архитектура в этом плане узковатая, хотелось бы еще увидеть примеры не только в React

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

На счет 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 это тоже инструмент и он не всегда подходит.

Я искренне считаю, что это очень крутая архитектура. Очень простая.

Использовать Effector и Redux в 2024 году. С такими инструментами она априори не может быть крутой и простой. Открою вам страшную тайну, в связке с React'ом можно и нужно использовать только MobX, тут без альтернативно, разве что только самописный аналог.

Вот это я понимаю реально супер просто и круто:
https://stackblitz.com/edit/vitejs-vite-y2qj7g?file=src%2FApp.tsx&terminal=dev

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

Если прочесть статью, то там я как раз таки это и пишу, что Redux и Effector не нужны, потому что они дают слишком много чем мне нужно и самый лучший вариант писать что-то своё.

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

Казалось бы, мы просто создаем классы с данными, методы для работы и всё круто. Никаких хуков для работы с этим не нужно, просто экспортируем в компоненты и радуемся жизни.

makeAutoObservable, observer и всё работает.

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

Я бы просто попросил, если вам это интересно, потратить парочку часов и реализовать простенькое любое приложение с полностью MVA подходом.

Да даже тот пример который скинули вы просто переписать, но лучше что-то более реальное.

Создаете отдельно Action. Например получение списка постов. Это просто запрос на сервер, валидация данных их возвращение.

Создаете парочку Model. postsIsPending и postsList, например.

Создаете Effect обертку над Action, подписываетесь на неё моделями и всё.

Дальше просто рендерите эти данные.

----

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

Я просто делюсь своими идеями.

Я да, я так же как и с Redux, Effector, считаю что и MobX не нужен) Просто на нем вроде как нельзя реализовать "просто" то о чем писал я, по этому я о нем и не писал.

Да даже тот пример который скинули вы просто переписать, но лучше что-то более реальное.

Создаете отдельно Action. Например получение списка постов. Это просто запрос на сервер, валидация данных их возвращение.

Создаете парочку Model. postsIsPending и postsList, например.

Создаете Effect обертку над Action, подписываетесь на неё моделями и всё.

Дальше просто рендерите эти данные.

Вот вариант с постами
https://stackblitz.com/edit/vitejs-vite-3ajbxs?file=src%2Fpages%2Fmain%2Findex.tsx,src%2FglobalState%2Fposts%2Findex.ts&terminal=dev
Там всё реально ультра просто и очевидно:

Нажать сюда
Нажать сюда

Минимум лишних файлов, папок и кода, и главное всё предельно наглядно и очевидно.

Да, я полностью понимаю о чем вы, я сам раньше делал так же.

  • Но а что если вы захотите использовать эту функцию загрузки постов еще где-то, но без того, чтобы что-то обновлялось?

Нужно её выносить в отдельный дополнительный метод или функцию (файл).

  • А что если вы захотите при logout очищать массив постов?

Вам нужно будет из некого class Auth из метода logout дергать PostsState.clear()? А потом не только посты, а еще десятки других классов и методов?

Всё в итоге это становится более запутанным..

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

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

****

Тут конечно упрощенный код, но это не важно.

Вы сделали так как сделали, а можно сделать так:

// src/action/posts/getPosts

const getPosts = function () {
  return fetch("https://jsonplaceholder.typicode.com/posts")
    .then((response) => response.json());
}

// src/model/posts

const getPostsEffect = effect(getPosts);

const postsLoading = store(false)
  .on(getPostsEffect, 'onBefore', () => true)
  .on(getPostsEffect, 'onFinally', () => false);

const postsList = store([])
  .on(getPostsEffect, 'onSuccess', (_, { result }) => result);

И теперь я могу использовать getPosts - где угодно и когда угодно.

Если мне нужно подгрузить посты - я просто вызываю getPostsEffect из любого места в любое время и всё.

Если мне нужно очищать postsList при logout я не лезу в какие-то другие классы и ничего там не меняю/дописываю, я иду именно к postsList и дописываю подписку на logoutEffect .

const postsList = store([])
  .on(getPostsEffect, 'onSuccess', (_, { result }) => result)
  .on(logoutEffect, 'onBefore', () => []);

И вот они все случаи когда обновляется postsList .

А теперь давайте еще представим, что у нас появились уведомления.

К нам пришло уведомление о новом посте и мы хотим добавлять его в список постов.

const postsList = store([])
  .on(getPostsEffect, 'onSuccess', (_, { result }) => result)
  .on(addingPostNotification, 'onSuccess', (state, { result }) => [...state, result])
  .on(logoutEffect, 'onBefore', () => []);

Ну и так далее.

Я не лезу никуда для того чтобы откуда то обновлять postsList . Всё вон оно, в одном месте.

  • Но а что если вы захотите использовать эту функцию загрузки постов еще где-то, но без того, чтобы что-то обновлялось?

Вообще не понял в чём цимус, но окэй как говориться, так то элементарно

Вариант номер раз (отдельный метод внутри класса)
Вариант номер раз (отдельный метод внутри класса)
Вариант номер два (просто функция)
Вариант номер два (просто функция)

.Какой больше по душе, такой можно выбрать.

  • А что если вы захотите при logout очищать массив постов?

Вам нужно будет из некого class Auth из метода logout дергать PostsState.clear()? А потом не только посты, а еще десятки других классов и методов?

Всё в итоге это становится более запутанным..

Да нет, можно сделать ровно так же как вы сделали в своем примере ниже в этом же комментарии

Раз
Раз
Два
Два
 ()Ну и чтобы их дергать
()Ну и чтобы их дергать

И вуаля, всё так же как у вас, одном месте, рядом с данными, просто в традиционном нативном для js стиле.


Вот сам код https://stackblitz.com/edit/vitejs-vite-ffchmx?file=src%2Fpages%2Fmain%2Findex.tsx&terminal=dev

Бонусом там же ещё сделал чтобы все асинхронные функции в классе имели реактивные свойства fetching, error, callsCount (лежит в helpers/makeReactive)

Так я уже ведь всё написал, что хотел..

Если вы понимаете, что то как вы делаете - лучший для вас вариант, то используйте его. Я предложил лишь новый инструмент, который, разумеется, подойдет не в каждом случае и не для каждого.

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

Плюс статья то не про реализацию.. а про архитектуру..

----

Но даже если посмотреть на новый код который вы скинули, он уже сильно сложнее и больше чем тот что показывал я..

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

Да.. вы можете его вынести отдельно.. и вызывать его в методе fetchPosts внутри класса.. Опять же.. это даже выглядит как что-то лишнее..

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

----

Если взять ваш последний код, вынести fetchData, logout и createNewPost в отдельные функции, и добавить внутрь них .emit -теры, а внутри моделей подписываться на вызовы этих эмиттеров, то, по сути, получится то что писал я, но.. ваши функции будут связаны с эмиттером.

Тогда мы можем сделать над ними обертку, да. И тогда вы получаете effect .

И вот вы сделали то что писал я, но с более сложной реализацией.

И теперь мы получаем самодостаточные функции, которые можно вызывать откуда и когда угодно, без подтягивания даже mobx-а в банлд.

В моделях остается только работа с данными без зависимости от чего-то внешнего (кроме EventEmitter в вашем случае), благодаря чему их легко можно тестировать без всяких там mock -ов.

----

Повторюсь.. Я не предлагаю что-то на все случаи жизни, что подойдет вам в любой ситуации для любого приложения. Я лишь предложил еще один вариант.

Я могу представить варианты когда такой подход можно реализовать так как написал я, я могу представить варианты при которых EventEmitter подойдет лучше, я могу представить варианты при которых ни один из этих вариантов не подойдет и нужно писать что-то под конкретную задачу с максимальной производительностью.

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

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

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

А в чём конкретно запутанность и сложность того примера, что вам скинули выше? Вот я его видел и не понимаю, честно.

Но даже если посмотреть на новый код который вы скинули, он уже сильно сложнее и больше чем тот что показывал я..

А чем он сильно сложнее? EventEmitter и вообще событийная модель это де факто стандарт в JS с лохматых времен, он что-ли сложный и запутанный? Ну если вам прям так нужно какое-то событие, чтобы на него подписаться, просто испусткайте его, а в подписке на него обрабатывайте, или это сложно и запутанно, может не очевидно?

Плюс если вы захотите использовать fetchPosts в каком-нибудь новом месте - вам туда нужно будет подгружать весь PostsState, а, возможно, вы этого не захотите

А разве там не было ответа на этот тривиальный вопрос?)

Это нельзя использовать в любом новом месте?

Да.. вы можете его вынести отдельно.. и вызывать его в методе fetchPosts внутри класса.. Опять же.. это даже выглядит как что-то лишнее..

Лишне? Я так понимаю сам факт написания кода выглядит как что-то лишнее..

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

Сложной?

events.on('myEvent', () => console.log('wow'));
events.emit('myEvent');

А начиная с какого года это стало сложным?

<button 
    onClick={() => console.log('wow')} 
    onMouseEnter={() => console.log('wow')}
>btn</button>

А вот это легко? Чем отличается от того, что выше?

  1. А в чём конкретно запутанность и сложность того примера, что вам скинули выше?

    Если мы берем именно пример который скинули - сложности никакой. Сложность возникнет при росте приложения.

  2. А чем он сильно сложнее? EventEmitter и вообще событийная модель это де факто стандарт в JS с лохматых времен, он что-ли сложный и запутанный?....

    Сильно сложнее чем тот что показывал я. Он всё еще не сложный. Смотрите пункт 1.

  3. А разве там не было ответа на этот тривиальный вопрос?)

    Я каким то образом не заметил и вообще впервые вижу эти скриншоты в комменатрии.. Возможно они просто у меня не прогрузились.. Так что да, это то что я и имел ввиду.

  4. Лишне? Я так понимаю сам факт написания кода выглядит как что-то лишнее..

    Писать отдельно функцию, а потом в каком то классе делать метод только для того, чтобы вызвать только эту функцию - выглядит лишним, да. Моё мнение.

  5. Сложной?

    "но тогда получится то что писал я, но с более сложной реализацией..".

    С более сложной реализацией. Не сложной. Более сложной.

  6. А начиная с какого года это стало сложным?

    Это не сложно) Если вы прочтете комментарий, то увидите там :
    " Я могу представить варианты когда такой подход можно реализовать так как написал я, я могу представить варианты при которых EventEmitter подойдет лучше ".

  7. А вот это легко? Чем отличается от того, что выше?

    Всем.

Если мы берем именно пример который скинули - сложности никакой. Сложность возникнет при росте приложения.

Сложность возникает везде и всегда при росте приложения, где-то она менее выражена, а где-то более Вы просто ткнули пальцем в небо и решили ваш вариант лучше без аргументов..

Сильно сложнее чем тот что показывал я. Он всё еще не сложный. Смотрите пункт 1.

Сложнее? Чем именно? Вы шутите или в серьез говорите? Я реально не понимаю

Я каким то образом не заметил и вообще впервые вижу эти скриншоты в комменатрии.. Возможно они просто у меня не прогрузились.. Так что да, это то что я и имел ввиду.

Очень удобно

Писать отдельно функцию, а потом в каком то классе делать метод только для того, чтобы вызвать только эту функцию - выглядит лишним, да. Моё мнение.

Так это же было так сказать ваше ТЗ, вы зачем то захотели отдельную функцию, чтобы получать данные и не реагировать на них для чего-то.

"но тогда получится то что писал я, но с более сложной реализацией..".

С более сложной реализацией. Не сложной. Более сложной.

Я вижу ровном счетом наоборот, ваша реализации более не явная и более сложная, т.к. надо знать и вникать в тонкости работы Effector, а в примере оппонента просто this.some = 'hello'; А это нативный код в который не надо вникать и изучать его работу.

Всем

Чем? Тем что мы тоже подписались на события?

Это не сложно)

Если всё это не сложно, то зачем вы говорите что это сложно, неудобно и т.п.? Ну этот код, где MobX он же объективно супер простой и понятный и не важно какого размера будет приложение, сам принципе не изменится, каждая функция всё так же будет очевидной и понятной, читаться сверху вниз, в чем проблема то?

Я так понимаю эта архитектура тяготеет к функциональному подходу? Совместима ли она с SOLID принципами? Или SOLID здесь не уместен?

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

Архитектура не диктует то с помощью каких инструментов вам нужно реализовать её.

Архитектура закладывает принципы, структуру, но как вы это реализуете - это уже решаете вы.

Если вы хотите использовать ООП, применять принципы SOLID для реализации, пожалуйста, вы это можете сделать.

Хорошо. Понятно.

Sign up to leave a comment.

Articles