All streams
Search
Write a publication
Pull to refresh
3
0
Send message
  • Здесь использована магия (не все любят магию) mobx с ООП (многие считают эту парадигму неудачной), обертками компонент (увеличение vdom, стектрейса). Вынесем это за скобки ибо холивар разводить не хотелось бы.

Object getter/setter магия?) Ну ок) Магия так магия)))
https://stackblitz.com/edit/vitejs-vite-fkgny3?file=src%2Fmain.ts&terminal=dev

  • Использована какая то неизвестная мне функция asyncHelpers для debounce и отмены запросов.

Ну да, она легко имплементируется самостоятельно, ее АПИ же видно и понятно как она устроена и работает

  • Использован неизвестный мне класс ApiReq (опять ООП) в тч для кэширования запросов.

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

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

Так то вообще легко, например можно в fetchData добавить аргумент force который в withCahe передаст null, что будет означать что кэш мы игнорируем и делаем запрос, опять же т.к. реализация своя можно всё что угодно добавлять.

  • fetching каждый раз переходит в true, а в конце в false, тем самым показывая на короткое время спинер даже если данные уже закэшированы - UI баг. Возможно потребуется серьезный рефакторинг чтобы это исправить, если не полный отказ от данной архитектуры. Тут нужно смотреть на реализацию ApiReq.

Т.к. MobX сконфигурирован на асинхронные реакции(включая автобатчинг), а они запускаются путём setTimeout, а внутри функции у нас промисты(микротаски), в момент испускания реакции значение fetching будет неизменным true и никаких спиннеров на короткое время не будет.
Вот как выглядит данная конфигурация

Вот как это в действии https://stackblitz.com/edit/vitejs-vite-vslbhn?file=src%2Fmain.ts&terminal=dev

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

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

  • Данные item можно показать еще пока не погрузились комментарии, но не в данном примере. Потребуется рефакторинг.

Легко, просто код чутка измените под эти нужды и всё, элементарно же.

  • Легко ли прикрутить, например, персистентность?

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

Именно поэтому лучше не городить "свои решения", а использовать библиотеку, где большинство моментов уже продумали.

Т.е. по вашему все вокруг идиоты, включая меня. И решения написанные нами это дно и их лучше не использовать. А вот если использовать библиотеку которую кто-то там написал(в том числе вы), вот уже совсем другое дело, там настоящий уровень и все так прекрасно и удобно. А если мне/Васе/Пете и т.п. не нравится то, что есть? Как быть? Ну не смешите такими выводами странными. Такие "выводы" канают только для начинающих.

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

Вы сами придумали "проблемы", которые кстати решаются по щелчку пальца, ибо описанное вами поведение нужно конкретно вам, а конкретно мне нужно другое поведение, а конкретно Васе нужно своё поведение и т.п.

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

Да любой проект, который писали опытные люди, в том числе классическое решение это функция/класс обертка для запросов к АПИ, в том числе события, на которые можно подписаться и перехватывать запросы/модицифировать их/универсальную обработку ошибок делать/ и т.д. и т.п. Так же классика это когда компоненты, которые используются в разных местах лежат в src/components и т.п.

А почему просто не использовать классику?

import { makeAutpObservable } from "mobx";

class SomeState {
  fetching = false;
  error = null;
  item: IItem = null;

  constuctor() {
    makeAutpObservable(this);
  }
  
  fetchData = async () => {
    this.fetching = true;
    const ah = asyncHelpers(this.fetchData);
    // Debounce + no race condition 300ms
    if (!await ah.debouce(300))  return;
  
    try {
      const item = await new ApiReq(`GET /api/v1/item/${this.itemId}`)
                                        .withCahe(60) // кэш 60сек
                                        // для отмены запросов
                                        .withAbort(ah.abortControllersArray)
                                        .send()
      // race condition check
      if (!ah.stillActual()) return;

      const itemComments = await new ApiReq(`GET /api/v1/item-comments/${item.commentsId}`)
                                        .withCahe(60) // кэш 60сек
                                        // для отмены запросов
                                        .withAbort(ah.abortControllersArray)
                                        .send()
      // race condition check
      if (!ah.stillActual()) return;

      const data:IItem = {
        ...item,
        comments: itemComments
      }

      this.item = data;
      this.error = null;
    } catch (e) {
      // race condition check
      if (!ah.stillActual()) return;

      this.error = e;
    } finally {
      // race condition check
      if (!ah.stillActual()) return;

      this.fetching = false;
    }
  }
}

Ну и дальше в компоненте

const MyList = observer(() => {
  useState(() => { someState.fetchData(); });
  if (someState.fetching) return <Spinner />

  return (
    <div className={styles.list_container}">
      {someState.map(item => <div className={styles.list_item}>...</div>)}
    </div>
  )
});

А что насчет debouce?
Что насчет race condition?

А почему просто не использовать классику?

import { makeAutpObservable } from "mobx";

class SomeState {
  fetching = false;
  error = null;
  item: IItem = null;

  constuctor() {
    makeAutpObservable(this);
  }
  
  fetchData = async () => {
    this.fetching = true;
    const ah = asyncHelpers(this.fetchData);
    // Debounce + no race condition 300ms
    if (!await ah.debouce(300))  return;
  
    try {
      const item = await new ApiReq(`GET /api/v1/item/${this.itemId}`)
                                        .withCahe(60) // кэш 60сек
                                        // для отмены запросов
                                        .withAbort(ah.abortControllersArray)
                                        .send()
      // race condition check
      if (!ah.stillActual()) return;

      const itemComments = await new ApiReq(`GET /api/v1/item-comments/${item.commentsId}`)
                                        .withCahe(60) // кэш 60сек
                                        // для отмены запросов
                                        .withAbort(ah.abortControllersArray)
                                        .send()
      // race condition check
      if (!ah.stillActual()) return;

      const data:IItem = {
        ...item,
        comments: itemComments
      }

      this.item = data;
      this.error = null;
    } catch (e) {
      // race condition check
      if (!ah.stillActual()) return;

      this.error = e;
    } finally {
      // race condition check
      if (!ah.stillActual()) return;

      this.fetching = false;
    }
  }
}

Ну и дальше в компоненте

const MyList = observer(() => {
  useState(() => { someState.fetchData(); });
  if (someState.fetching) return <Spinner />

  return (
    <div className={styles.list_container}">
      {someState.map(item => <div className={styles.list_item}>...</div>)}
    </div>
  )
});

Спасибо за подборку!

Разные СТМ по-разному кэшируют вычисления, что может сильно повлиять на результаты бенчмарка

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

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

В реальности у меня например сконфигурирован авто батчинг и реакция в этом примере была бы вызвана только 2 раза, в начальный момент и после синхронных изменений. Это всё туда же, к вопросу о том, что якобы mobx может быть источником тормозов.

Ну вот кстати с автобатингом - https://stackblitz.com/edit/vitejs-vite-vslbhn?file=src%2Fmain.ts&terminal=dev

Сразу в 4 раза быстрее на ровном месте.

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

При чем тут вообще computed'ы?)) Мы опять про желтое с мягким говорим?))

Но опять же, не вопрос, вот с computed'ом в довесок - https://stackblitz.com/edit/vitejs-vite-m3ixjp?file=src%2Fmain.ts&terminal=dev

Речь идёт про типичный сценарий, computed это вообще частный случай, более того изначально была речь о том, что mobx весь такой медленный, а $mol_wire весь такой быстрый, и что мы имеем в итоге?

Как думаете, теперь мы увидим вариацию с $mol? Правильно, нет. Максимум что мы увидим - новую отмазку.

Спасибо за подборку!

Вам про машину говорят, а вы про лопату твердите, что мол черенок у нее не того цвета.

Как только речь зашла о том чтобы код конкретный показать в stackblitz, так сразу в кусты. Отмазки пошли из детского сада и ранней школы. Смешно.

Потом ещё удивляетесь почему никто в здравом уме не рассматривает и никогда не будет рассматривать ничего, что связано с $mol.

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

1) Чтение = вызов getter'a, проверка на то надо ли куда подписываться или нет, и если надо подписка. Реактивная система задействована.
2) Запись - вызов setter'a, проверка изменилась ли значение, если изменилось, то вызываем реакции. Реактивная система задействована.
3) На каждой итерации в примере вызывается реакция. Собственно сама реакция в реактивной системе.

В моём примере каждый раз полный цикл реактивности задействован. А вы говорите что он не задействована вообще, смешно. Плюс весь смысл измерить накладные расходы инструмента, а не скорость JS'a, т.к. в функции реакции можно было сделать цикл где 100млн раз дописывался бы символ 'a' к строке, и что бы мы тогда измерили? Правильно, скорость работы этой функции, а не MobX, $mol и т.п.


P.S. Всё понятно, значит вариант с $mol_wire просто медленнее, иначе вы бы не соскакивали с темы. Обидно когда с пеной у рта кричишь что твоя вариации самая быстрая, а в итоге нет.

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

Не надо опускаться до уровня @artalar
Сделайте пожалуйста, все вместе посмотрим, там реально делов на 5 минут от силы, я бы сам сделал но не разобрался как.
Вот в том же Supercat Store всё понятно, и я тоже сделал с ним вариант:
https://stackblitz.com/edit/vitejs-vite-k6i3qw?file=src%2Fmain.ts&terminal=dev
Он оказался помедленнее MobX, но не критично. @supercat1337

Или вы всё же сделали и результат оказался таким же по скорости или медленнее и вы решили не кидать ссылку? Тогда это вообще не спортивное поведение.

Я вот даже версию с reatom сделал
https://stackblitz.com/edit/vitejs-vite-rnuwpv?file=src%2Fmain.ts&terminal=dev

Да, он побыстрее работает, но платить за это такую цену в виде написания кода какой он предлагает - нет, спасибо. Очень не хватает $mol_wire для сравнения

Ну и можно пример реального приложения в котором "разницы нет"?

Давайте лоб в лоб, вот на таком примере:
https://stackblitz.com/edit/vitejs-vite-js4kjx?file=src%2Fmain.ts&terminal=dev

И то он далеко не из реальной жизни, а завышен многократно, за тик 1000 мутаций, чтений, реакций и все это укладывается всё равно в 3ms у меня, на древнем железе будет 6ms. В реальности делите это на 50-100, а 1% случаев пускай на 10.

А время кадра браузера (60fps) 1 / 60 = 16.6ms, даже в таком нереальном кейсе запас в 13ms. Отсюда и получается что разницы нет.

Сделайте тоже самое на $mol_wire (я не разобрался как, пытался, но ничего не взлетело) и пришлите ссылку на stackblitz.

Спасибо за статью, познавательно и интересно!

Диабет второго типа сильно связан с ожирением

Разумеется

В борьбе с ожирением питание гораздо важнее физ нагрузок. Увы.

Да, но в сочетании обоих этих факторов результат ощутимо лучше, бонусом здоровье сердечно сосудистой системы улучшается. Диета да, нужна и это основа, но если добавить к ней регулярные физ нагрузки, то результат будет лучше, вот в чем дело. Не на 1-2% лучше, а прям лучше.

Оптимальная диета для отличного самочувствия

А почему ни слова про физические нагрузки? Диеты диетами, но регулярные кардио и силовые так же нужны нашему организму, и вот в сочетании физические нагрузки + адекватное питания и получается отличное самочувствие. Плюсом физ нагрузки увеличивают чувствительность клеток к инсулину, что так же благотворно влияет на профилактику диабета 2 типа.

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

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

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

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

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

Очень удобно

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

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

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

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

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

Всем

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

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

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

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

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

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

А чем он сильно сложнее? 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>

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

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

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

Статья хороша. спасибо!

обла1дает обширной

А ещё у вас опечатка)

Примерно так и было. В статье я как раз пишу, что мы выбрали Effector отчасти "на волне хайпа". И это было ошибкой.

Спасибо что не стесняетесь признавать ошибки, как большинство

Сейчас переводим на новую архитектуру, которая для управления состоянием использует MobX.

Надеюсь вам понравится, но сильно удивлюсь если вдруг нет)

В данном случае речь о том, что есть такой тренд есть в среде React-разработчиков (к которым мы относимся) и он достаточно популярен 

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

class UserState {
  user: IUserData = null;
  authorized = false;
  isAdmin = false;
  
  fetchUserData = async () => {...}
  logout = async () => {...}
}

export const userState = new UserState();

Всё лежит максимально логично и правильно, просто читай свойства и вызывай методы, что тут плохого?? Я не понимаю в чем смысл вместо этого, писать вот такого рода каракули, и это ещё только hello world пример:

const event = createEvent<EventType>();

const $store = createStore(initialValue);

const effect = createEffect((...) => { ... });

sample({
  source: event,
  target: effect
});

sample({
  source: effect.doneData,
  fn: (...) => { // трансформируем результат эффекта },
  target: $store
});

Что должно случиться в жизни, чтобы прийти к такому?)

Учитывая, что в современном FE разработчики (почему-то) избегают классы

Что прям все?) Или 99%?) А если взять самый очевидный пример - Angular, там тоже классов избегают?)

и предпочитают react-like код

Опять же кто?) Да, такие люди есть, но с чего это их мнение правильное? React это view слой, да, JSX вышла удачная штука, но управлять состоянием с помощью него - рыть себе могилу сразу же.

Получается вы попали в классическую ловушку, увидели что где-то кто-то делает вот так, увидели какие-то статьи и всё, ваша картина мира сложилась и вы поняли что да, это именно то, что нужно)))

Неужели так сложно понять что это гуано не пробуя его на вкус? Например по виду и запаху? Это же сразу очевидно когда смотришь на код эффектора и на этот подход.

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

Information

Rating
Does not participate
Registered
Activity

Specialization

Frontend Developer
Lead
TypeScript
JavaScript
React
Node.js
MobX