Pull to refresh

Comments 79

По вашему вот этот вырви глаз "код" и "подход", кодом этого конечно можно с большой натяжкой называть. Хоть на грамм может что-то сопоставить MobX'у? Да никогда, между ними пропасть в триллионы световых лет. Даже убогий Redux на фоне Effector выглядит гораздо лучше, т.к. там все гораздо очевидней.

Если ваша цель запороть проект с самого начала, то да, возьмите Effector, пусть у всех взорвется мозг и будет взрываться дальше в геометрической прогрессии с добавлением каждой новой фичи и с каждым расширением функционала. С таким же успехом вы можете взять RxJS, результат будет одинаковым и НЕжизнеспособным.

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

Хоть на грамм может что-то сопоставить MobX'у?

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

В эффекторе же у вас есть только события, которые способны изменять стор.
Effector будет полегче того же МобХ почти в 2,5 раза (говорю про связку с реактом).

вырви глаз "код" и "подход"

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

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

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

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

"В эффекторе же у вас есть только события, которые способны изменять стор" - ну и в Mobx есть функции-экшены, которые изменяют стор, только без необходимости иммутабельно писать как в редаксе (store, data) = > ({...store, items: store.items.map(item => ({...item, isViewed: data.id === item.id}))}) , разводя дубляж и значительно усложняя код, и заводя прослойку в виде событий, которые являются чистым бесполезным бойлерплейтом. Все, что нужно - вызвать функцию, она меняет нужный параметр в сторе, автоматически происходит эффективный перерендеринг.

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

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

import { configure } from 'mobx';

configure({
    enforceActions: 'never',
});

Разумеется нужно делать так.

Вам видимо никогда не приходилось искать кто изменил какую-то переменную в сторе. Ставить костыли через itercept или того хуже spy. Иначе вы бы так не говорили))

Вам видимо никогда не приходилось искать кто изменил какую-то переменную в сторе. Ставить костыли через itercept или того хуже spy. Иначе вы бы так не говорили))

Ну в 99.99% случаях я знаю что делаю, когда пишу код) И знаю какая переменная когда и зачем должна изменятся и читаться, что при этом будет происходить и тп)
А вообще всё просто, щелкаешь на переменную. правой кнопкой мыши и:
WebStorm:

VS Code:

Там видно где переменная читается, где она мутируется. Если этого оказалось не достаточно для понимания того места, которое вы ищете, то опять же всё просто, на помощь приходит console.log. Весть процесс дебага в самом худшем сценарии занимает не более 5-10 минут. Но у меня например за 6 лет использования MobX приходилось не более 3-4 раз прибегать к такому дебагу.

Поэтому я считаю на все 100000% оправданно вырубать enforceActions чтобы не засирать код. По той же причине (не засирать код) я и runInAction(() => {}) не использую после асинхронных вызовов если мне надо смутировать более 1 реактивной переменной, ибо раньше у меня был автоматический батчинг настроен всегда, а начиная с react 18 и он стал не нужен, там и так рендер не синхронный стал.

Тоже пользуюсь Find Usages, дебаг очень простой. А чтобы не было ворнингов все эшены автоматом оборачиваются в action, а для батчинга runInAction, если после асинхронных вызовов. Я пока не считаю, что оправданно вырубать enforceActions - многое в коде зависит от батчинга (мне нужно чтобы несколько переменных менялись сразу, а не постепенно - не для рендера в Реакте, а для autorun, которые например отслеживают форму - надо чтобы и value поменялись, и isFocused, и валидаторы, и авторан вызвался 1 раз с финальными данными). И таких ситуаций много. Плюс я стараюсь делать библиотеко- и фреймворко-независимую архитектуру, а в альтернативах MobX тоже используются колбэки системной функции для батчинга, иначе его просто не выстроить синхронно.

К теме дебага - все экшены у меня еще логируются в визуальный интерфейс (история вызова + время на исполнение) в виде stack bars chart, что максимально сужает поиск по Find Usages.

(мне нужно чтобы несколько переменных менялись сразу, а не постепенно - не для рендера в Реакте, а для autorun, которые например отслеживают форму - надо чтобы и value поменялись, и isFocused, и валидаторы, и авторан вызвался 1 раз с финальными данными). И таких ситуаций много.

Ладно, вот бесплатный лайфхак для таких целей) Если конечно конфиг с автобатчингом вас не устраивает)
Работает как часики)

function batchedAutorun(fn) {
  let timeOut = null;
  let firstRun = true;
  function batch() {
    if (firstRun) {
      firstRun = false;
      fn();
    } else {
      clearTimeout(timeOut);
      timeOut = setTimeout(fn);
    }
  }

  return autorun(batch);
}

Тоже самое для reaction по аналогии.

Вот быстро проверить протестить - https://codesandbox.io/s/summer-leaf-hkv0x9?file=/src/index.js

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

  configure({
    reactionScheduler: f => setTimeout(f, 1)
  });

Спасибо что скинули мой же конфиг) Только у меня чуть более усложнен) И в нем учитывается нюанс c controlled input'ами.

Если вам не нужны синхронные подписки - хорошо

Неа, не нужны, у меня всегда вся архитектура строится на том, что мы живем в асинхронном мире JS'a в браузере/на сервере)

синхронные изменения - это тоже очень важно

Изменения и так синхронные) А вот потребность именно в мгновенных синхронных реакциях на них, ну не знаю не знаю) Всё таки всё вращается вокруг асинхронности у нас)

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

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

  2. Цепочки авторанов (в первом меняется параметр, который слушает второй авторан, в третьем прослушивается результат второго) - хоть Реакт 18 и добавил асинхронности в рендер, я на это не полагаюсь, и на каждый рендер подготавливаю финальное состояние, чтобы отрендерилось 1 раз. Т.к. ориентируюсь на более-менее свободную замену частей приложения (в том числе рендерера типа Реакта), то важно синхронно подготовить данные для избежания ререндеров - это просто хороший тон и предотвращение проблем.

  3. Ну и минорное замечание к решению выше - если подменять одну функцию другой и передавать в setTimeout(fn) то контекст теряется и в стеке вызовов будет batch вместо реального имени функции. Фиксится парой строк, но все равно об этом думать надо

Оказывается, это Mazaa шифруется под новым ником, интересно)

В реальных задачах я могу вешать 5 авторанов последовательно, и даже если меняется их порядок - то где-то работает некорректно. Конечно, это антипаттерн и нужны прямые руки и продумывать лучше, но как это всегда бывает - нужно все срочно и потоки МР по 5-10к строк ежедневные, вангую, что с асинхронным вызовом было бы проблем больше, чем порядок поменять.

По пунктикам:
1) Вот именно что все отработает синхронно при первом вызове, а дальше чтобы не повисало тоже решается легко с помощью проверки на if (SSR_MODE) { return autorun(fn) } else { вариант с автобатчингом }

Так что аргумент в "против" не засчитан, т.к. с этой задачей справляется на ура)

2)

Цепочки авторанов (в первом меняется параметр, который слушает второй авторан, в третьем прослушивается результат второго) 


Цепочка авторанов влияющих друга на друга это антипаттерн) Можно загнать реакции в бесконечный цикл)

Ну и тоже относится очень сильно к вашему специфическому подходу) Абсолютно прекрасно любые задачи реализуются с асинхронными реакциями ан самом деле)

Т.к. ориентируюсь на более-менее свободную замену частей приложения (в том числе рендерера типа Реакта)


Ну это тоже не аргумент, на практике никто и никогда не меняет view с реакта на vue/angular/svelte, без конкретного переписывания, понятное дело что на peact можно сменить почти(!) безболезненно. И вы всё равно никуда не убежите от специфики работы с тем или иным view фреймворком. Так что тоже не засчитывается) Ну серьезно, кейсы из области фантастики или 1/1000000 в расчет никогда не берутся, иначе вообще никогда и ничто нам не подойдет.

3) Тоже из области фантастики и на практике разумеется не применимо, ибо тогда либо вы не понимаете что вообще делаете в этой жизни или дизайн вашего приложения слетел с катушек)

В общем для 99.99% проектов и людей, кроме вас предложенный мной вариант автобатчинга более чем подходит)) Хотя на самом деле и вам подойдет, т.к. единственный рабочий аргумент был про SSR, но и он элементарно контрится, поэтому оказался нерабочим аргументом)

Про "никто не меняет вью" в принципе согласен, но я скорее для спортивного интереса поддерживаю архитектурные части изолированными и абстрагированными. Не только Реакт могу поменять на Preact/Inferno/SolidJS и экспериментирую с заменой через адаптеры на другие рендереры (даже веб-компоненты), но и MobX на другие реактивные библиотеки, тот же Solid mutableStore. Это позволяет мне лично расти как специалисту и создавать долгоподдерживаемые проекты, заодно сравнивая перфоманс и набор фичей и оттачивая дизайн системы. И в целом стараюсь не завязываться на конкретные особенности (ex. версия определенной либы, которая рендерит асинхронно). И другие архитектурные части тоже делаю самодостаточными и независимыми. Поэтому хаки чисто для MobX в виде асинхронного автобатчинга не рассматриваю.

Конечно, можно и с ним жить, и утилиты подбить так, что будет и SSR, и нормальный стек вызовов, и соблюдена очередность реакций. Можно и на альтернативные библиотеки наворачивать такой функционал, но все равно мне ближе явный подход - где изменяется стор, оборачивать в batch (runInAction в mobx). Так добавляется бойлерплейт, но упрощается понимание за счет синхронности операций в и так непростом асинхронном мире JS, а в ряде случаев - улучшается перфоманс (незначительно, но в перегруженных компонентами страницах асинхронщина может привести к глитчам).

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

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

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

У меня тоже своя реализация роутинга с помощью MobX'a, react-router не уважаю, но там пофиг синхронно reaction отрабатывает или асинхронно) Ну суть понятна, у вас больше тягота к синхронности, а мне по большому счету не принципиально, но по умолчанию все под асинхронность проектирую и закладываю, наверное поэтому и проблем не знаю и ни с чем не борюсь)

А ничего, что ваш batchedAutorun выполняется ровно 2 раза?


Замените в вашем примере setTimeout на setInterval и посмотрите что происходит...


Заканчивайте уже херню советовать.

Это знак, что лучше избегать autorun и использовать reaction)

function batchedReaction(expression, fn, options) {
  let timeOut = null;
  function batch() {
    clearTimeout(timeOut);
    timeOut = setTimeout(fn);
  }

  return reaction(expression, batch, options);
}


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

Заканчивайте уже херню советовать.

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

Или же можно просто передавать { delay: 1 } в качестве options вместо нагораживания своих велосипедов...

Так не интересно, это слишком просто. А так да

autorun(() => {
    // ...
}, { delay: 1 });

did the trick

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

Досмотрел этот тред и после всех этих обсуждений типа "да тут надо подтюнить в конфигах и обернуть в все в функцию, которая будет пропатчивать что-то, чтобы ворнинги не сыпались" начальное утверждение "да mobx в тысячу раз проще" выглядит уже не таким однозначным =)

На самом деле там всё просто, главное — не начинать читать комментарии с "добрыми" советами

главное — не начинать читать комментарии с "добрыми" советами

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

Это просто были рассуждения над специфическими кейсами @DmitryKazakov8 из-за его сильной любви к максимально возможной синхронности))

Для всех остальных(в том числе меня) всё предельно просто:

import { configure } from 'mobx';

let reactionSchedulerTimeout = null;

// Configure MobX to auto batch all sync mutations without using action/runInAction
configure({
    enforceActions: 'never',
    reactionScheduler: (f) => {
        clearTimeout(reactionSchedulerTimeout);
        reactionSchedulerTimeout = setTimeout(f);
    },
});

И готово.
Это если вы не утопаете в легаси и используете React 18, а если ниже версия, то для input'ов реакция должна отрабатывать синхронно. Это тоже легко решается, просто за счет расширения React.createElement. Если актуально могу целиковое решения для React младше 18 версии приложить.

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

Нет ни одной задачи во фронтенде, где React + MobX может не справится или может не подойти. За исключением тех, где нужно сэкономить каждую наносекнду и каждый байт памяти. Но тут в вэбе не разгуляться, только голый JS.

 и под конкретную команду.

Тоже не совсем верно, т.к. если вашу вкусы скажем так специфичны в текущий момент времени, и потом вы увольняетесь/уходите на другой проект, то потом ваши так сказать вкусы должен будет расхлебывать кто-то другой. Есть большая проблема, одно дело код написан по простому, вот прям реально все просто, все читается слева направо и сверху вниз, заходишь в любой обработчик события и в нем же описано, что откуда мы взяли, куда что записали, куда какие запросы послали. Так вот, когда код написан так, то проблем вообще никогда не возникает, а вот когда вы начинаете накручивать лапшекод, запутываете элементарные вещи, притаскиваете всевозможный мусор типо effcotr'a, rxjs, xstate и т.д и т.п., то проект становится не жизнеспособным и способен хоть как-то худо бедно держаться на плаву, пока вы его поддерживаете.

Например, неявные подписки на наблюдаемые изменения

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

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

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

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

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

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

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

Effector будет полегче того же МобХ почти в 2,5 раза (говорю про связку с реактом).

Очень смешно когда в качестве "аргумента" ставят десяток килобайт в бандле, который капля в море в реальных проектах. Ещё больше смешно, когда не называют конкретные цифры, а говорят в 2.5 раза, как бы приувиличивая значимость. Более того, на самом деле то, что в MobX кода минимально возможное количество, то опять же на реальном проекте эта ничтожная разница компенсируется и уходит в профицит.

Подход, действительно, необычный. Я упоминал в статье, что первые полгода не мог привыкнуть.

Пол года Крал, пол года. С MobX открываешь документацию, потом 3.5 примера и все. через 3 часа ты как рыба в воде. О чем это говорит? Правильно, о том, что там все супер просто, понятно и наглядно. Изменил переменную, компоненты которые ее читают перерендерились, точка. Финита ля комедия.

Про "вырви глаз код", здесь каждому свое, по мне так код effector один из наиболее приятных.

Оно и понятно собственно)

Вот пример убогого и никчемного кода с MobX:

// Что тут произходит? Не понятно же нифига!
this.isFetching = true; 
// Что тут произходит? Не понятно же нифига!
this.items = await apiRequest('/some/data');
// Что тут произходит? Не понятно же нифига!
this.isFetching = false;

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

Только дело ваша любимая лапша из цепочек функций, ммм крастота то какая, ляпота. А $ в названиях переменных так это вообще вишенка на торте. Понимаю, понимаю)

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

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

отмену ненужных запросов (через cancellationToken например)

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

Браузер открывает TCP соединение, отсылает первые байты с данными в виде HTTP заголовков и тела запроса(если имеется), в это момент Nginx(или что угодно) проксирует этот запрос на бэкенд приложение(Node.js, PHP, C#, anyting else) и бэкенд начинает его обрабатывать, всё, обратного пути нет, но не может чудесным образом прерваться где-то в серединке. Он его обработает в любом случае целиком и полностью, но если вы его там прервется в браузере, то вы просто не получите ответа, любой супер тяжелый или нет запрос в БД улетит, отменяй/не отменяй это на клиенте. Так что это полная чушь и иллюзия для тех, кто не понимает как вообще работает бэкенд.

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

fetchData = async () => {
  const canContinue = debouncer(this.fetchData);
  if (!await canContinue(500)) return false;
  
  this.isFetching = true;

  try {
    this.items = await apiRequest('/some/data');
    this.error = null;
  } catch (e) {
    this.error = e.message || 'Что-то пошло не так';
    console.error(e);
  } finally {
    this.isFetching = false;
  }
}

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

Надеюсь как реализовать функцию debouncer вы понимаете, но если нет, могу дополнительно поделиться реализацией, мне не жалко.

Но если всё таки вы ярый фанат ненужных отмен, то это так же элементарно реализуется. И код будет выглядеть так:

fetchData = async () => {
  const utils = asyncUtils(this.fetchData);
  if (!await utils.debounde(500)) return false;
  
  this.isFetching = true;

  try {
    /* 
      Где utils.cancelToken = { value: null } т.к. нам нужно передать 
      ссылку на объект чтобы в нее apiRequest записал токен для отмены
    */
    this.items = await apiRequest('/some/data', utils.cancelToken);
    this.error = null;
  } catch (e) {
    this.error = e.message || 'Что-то пошло не так';
    console.error(e);
  } finally {
    this.isFetching = false;
  }
}

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

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

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

Например, ASP.NET Core это может выглядеть примерно вот так:

[HttpGet]
public async Task<ActionResult> GetHardWork(CancellationToken cancellationToken)
{
    await HeavyOperation1(cancellationToken);
    cancellationToken.ThrowIfCancellationRequested();
    await HeavyOperation2(cancellationToken);
}

Не везде и не всегда это имеет смысл, но забывать об этом не стоит.

Самое страшное что вы правда в это верите и считаете применимым в реальности(( Будущее разработки в опасности(

А теперь будем честны сами с собой - 99.99% Вообще не испытывают никаких нагрузок и вообще без разницы, отправили вы лишний запрос или не отправили. Даже самая дешманская виртуалка будет легко обрабатывать ~1000 RPS и больше при условии что вы специально не пишете супер медленный код, супер не оптимальные запросы и т.п. Так вот 99.99% в пике имеют порядка 50 RPS, а в среднем это число не доходит до 10.

Теперь касаемо остальных: 0.01% в которых RPS хотя бы переваливает за 500, что тоже на самом деле является ничтожной цифрой с точки зрения вычислительных мощностей. А уж если они не развернуты на нормальной машине, так там не напрягаясь держаться тысячи и десятки тысяч RPS.

1) Никто никогда не пишет и не будет писать разумеется бэкенд, который после каждой строчки проверяет отмену на клиенте. Просто представьте себе этот шматок Г, который ещё убьет свои производительность на ровном месте.
2) Bottle neck это БД, а не бэкенд(c#, php и т.п.) что возводит в степень идиотизм в проверке на отмену.
3) В базах данных нет никакой отмены. Расслабьтесь. Скормили ей тяжелый запрос, она будет его выполнять пока не закончить, т.к. он синхронный! И выполняется в отдельном потоке/процессе в зависимости от БД.
4) Отменяй не отменяй, так же легко организовывается DDOS, которому пофиг на ваш говнокод с отменой на клиенте)

А теперь будем честны сами с собой

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

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

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

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

После каждой строчки, очевидно, и не надо. Достаточно только перед началом каждой тяжёлой операции и/или её чанка. Часто это делается автоматически библиотеками/фреймворком.

Просто представьте себе этот шматок Г, который ещё убьет свои производительность на ровном месте.

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

2) Bottle neck это БД, а не бэкенд(c#, php и т.п.) что возводит в степень идиотизм в проверке на отмену.

Запросов в БД может быть больше одного. Запросы в БД можно отменять. Кроме БД есть ещё очереди, файловые и объектные хранилища, вызовы API других сервисов и т.д. Всё это могут быть достаточно дорогие операции и лишний раз их выполнять не нужно.

3) В базах данных нет никакой отмены. Расслабьтесь. Скормили ей тяжелый запрос, она будет его выполнять пока не закончить, т.к. он синхронный! И выполняется в отдельном потоке/процессе в зависимости от БД.

В базах данных есть отмена. Например, PQcancel в PostgreSQL. Более того, результат запроса из базы возвращается обычно потоком и считывается драйвером постепенно. Чем раньше вы отмените запрос, тем меньше нагрузка на базу.

4) Отменяй не отменяй, так же легко организовывается DDOS, которому пофиг на ваш говнокод с отменой на клиенте)

Никто вроде и не говорил, что отмена запроса поможет как-то защититься от DDoS.

Сначала лирическая часть, относится к 99.99% всех вэб проектов:

Даже если вы через какие-то дикие костыли худо бедно, в каком-то виде сможете убивать/останавливать запросы в БД(именно чтобы БД перестала тратить ресурсы на выполнение конкретного запроса), это вообще никакой роли не играет от слова совсем, т.к. мы же говорим про 99.99% проектов, про реальность, а не вымышленную популярность и загруженность, RPS пиковый 20-50 это просто комарик на слоне, при условии просто одной обычной виртуалки самой стандартной. +-1000 RPS в зависимости того насколько вы умеете проектировать схему БД, писать запросы и т.п. вообще на изи переваривается этой же виртуалкой, проверено многократными тестами само лично и нагрузочными в рамках компаний в которых работал. А если реально шарите и знаете что БД должна находится на Dedicated сервере и обладать 100% русурсами железа и систему, то уж извините, тут уже десятки тыщ это просто как семечки. Так вот, о опять возвращаемся в реальность да, 99.99% это в пиках ну 50 RPS, ну так и быть в аномальный промежуток времени пускай 300 RPS, это просто ни о чем, это просто около нулевая нагрузка на базу. БД способна обслуживать тысячами запросы, десятками тысяч, а на современном желе, а не на том что из 2010ых так уже наверное и к стоням тысяч походят циферки.


Теперь часть для 0.01% проектов, да даже меньше конечно, 0.01% это преувеличение:

Ремарка, когда речь идет о по настоящему высоконагруженных проектах (средний RPS в течении суток хотя бы > 5 000 и пиковые всплекски > 25 000), то безусловно для них очень атуально экономить ресурсы и процессорное время, в том числе отменять запросы, вместо всякого дерьма типа ORM писать запросы нормально и т.д и т.п.

Далее речь о чем-то промежуточном:

Давайте тут порассуждаем уже конкретно.
1) Вот вы говорите тяжелая операция.
- Тяжелая это сколько по процессорному времени? 1 секунда? 1 милисекунда? 10 микросекнуд? Давайте определимся с точной цифрой для оценки этого понятия.

2) Какой RPS в этом вашем проекте, какой RPS был максимальный на каких-то из ваших проектах?

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

Просьба на второй вопрос не писать наглую ложь и ответить честно. Я то легко отвечу чество, за всю мою карьеру, на продакшенах самый пикой RPS который я видел на мониторинге это что-то около 40, но это прям кратковременно, а средней в течении суток - 2 с хвостиком, это максималка которую я наблюдал ан разных проектах. Если бы RPS был выше в 20 раз, бэкенд бы вообще не напрягаясь переварил. Но тут ещё ремарка, это всё про то как я пишу код, архитектуру строю и т.п. Есть люди и "технологии" у которых на 20 RPS всё загнется, без шуток, лично такое наблюдал, было и очень смешно и очень грустно одновременно. В IT гигантах типо гугл и яндекс не работал, да и как бы не хочу. Там своих заморочек айда ушел)

если вы через какие-то дикие костыли худо бедно, в каком-то виде сможете убивать/останавливать запросы в БД

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

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

Да без разницы, путь даже поддерживают(в чем я сомневаюсь, что прям все там чики пуки и вот прям прерывают выполнение синхронного кода БД(конечно нет)), что с того-то? Какая вам и 99.99% с этого польза? Для RPS не достигающих хотя бы нескольких тысяч, вообще нет никакой разницы если вы разгрузите бэк благодаря абортам на 1-3% в лучшем случае.

Я вам по секрету скажу, в нормальных фреймворках прикладнику об этом вообще думать не надо - оно само работает как надо.

запрос на бэкенд приложение(Node.js, PHP, C#, anyting else) и бэкенд начинает его обрабатывать, всё, обратного пути нет, но не может чудесным образом прерваться где-то в серединке

Правильно написанный бэкенд может прерываться и на середине.

Надеюсь как реализовать функцию debouncer вы понимаете, но если нет, могу дополнительно поделиться реализацией, мне не жалко.

fetchData = async () => {
  const utils = asyncUtils(this.fetchData);
  if (!await utils.debounde(500)) return false;

Для такого случая и правда интересно - как хранится состояние, ведь utils пересоздается при каждом вызове fetchData.

fetchData = async () => {
  const utils = asyncUtils(this.fetchData);
  if (!await utils.debounde(500)) return false;

Для такого случая и правда интересно - как хранится состояние, ведь utils пересоздается при каждом вызове fetchData.


Неа, utils не пересоздается, а получает ссылку на один и тот же объект, вот подсказка:

const asyncUtilsMap = new WeakMap();
function asyncUtils(fn){
    let utils = asyncUtilsMap.get(fn);
    if (!utils) {
        utils = {
            debounce: getDeboundFn(fn),
            noRaceConditions: getNoRaceConditionsFn(fn),
            cancelToken: { value: null }
        }
        asyncUtilsMap.set(fn, utils);
    }
    
    return utils;
}


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

function asyncUtils(fn){
    let utils = asyncUtilsMap.get(fn)

Интересно, тут не будет проблем с методами как в примере выше? В JS используется наследование прототипов и один и тот же экземпляр функции будет разделяться разными оюъектами, которые этот класс породил.

class A {
  fetchData() {
     const untils = asyncUtilsMap.get(this.fetchData) 
  }
}
let x = new A()
let y = new A()
x.fetchData === y.fetchData // true 

(по правде говоря я не проверял, но в теории это должно так работать).

Вызывая asyncUtils(this.fetchData) из разных объектов вы получите один и тот же экземпляр utils. Соответственно и состояние denounce внутри будет общим. Приятной отладки)

P.S. Я бы рад отвечать быстрее, но из-за всяких недалеких, обиженных жизнью,

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

Интересно, тут не будет проблем с методами как в примере выше? В JS используется наследование прототипов и один и тот же экземпляр функции будет разделяться разными оюъектами, которые этот класс породил.

Вы обратите внимание, я писал в примерах именно стрелочную функцию как метод класса, для не будет прототипного наследования, а значит и проблем о которых вы написали. Следовательно никакой отладки и никаких проблем)

fetchData = async () => {...}

Если хочется или принципиально нужно именно прототипное наследование для таких функций, то код будет выглядить так:

class A {
  fetchData() {
     const untils = asyncUtils(this, 'fetchData or any unique key for this class') ;
  }
}

Тут this у каждого экземпляра свой собственный, отсюда и коллизий не будет. А универсальная реализация asyncUtils будет выглядить так:

const asyncUtilsProtoMap = new WeakMap();
function asyncUtilsProto(context, key){
    let utils = asyncUtilsProtoMap.get(context);
    if (!utils) {
        utils = {};
        asyncUtilsProtoMap.set(context, utils);
    }

    if (!utils[key]) {
        const target = {};
        utils[key] = {
            debounce: getDeboundFn(target),
            noRaceConditions: getNoRaceConditionsFn(target),
            cancelToken: { value: null },
        }
    }

    return utils[key];
}

const asyncUtilsArrowMap = new WeakMap();
function asyncUtilsArrow(fn){
    let utils = asyncUtilsArrowMap.get(fn);
    if (!utils) {
        utils = {
            debounce: getDeboundFn(fn),
            noRaceConditions: getNoRaceConditionsFn(fn),
            cancelToken: { value: null },
        }
        asyncUtilsArrowMap.set(fn, utils);
    }

    return utils;
}


function asyncUtils(context, key = null){
    if (key) return asyncUtilsProto(context, key);

    return asyncUtilsArrow(context);
}

У вас манера высказываться довольно специфическая, хотя как специалист вы наверное хороший.

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

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

Проблема-тио не во мнении, а в тоне.

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

@mem // cache and automatic dispose
items() {
  
  sleep( 500 ) // debounce
  
  try {
    
    return apiRequest( '/some/data' ) // automatic request cancellation
  
  } catch( error ) { // handle any error
    if( error instanceof Promise ) throw error // suspense api
    
    console.error( error )
    return [] // fallback
    
  }
  
}

Форматирование вышло из чата?)

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

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

Очень смешно когда в качестве "аргумента" ставят десяток килобайт в бандле, который капля в море в реальных проектах. Ещё больше смешно, когда не называют конкретные цифры, а говорят в 2.5 раза

Ваша проблема решается минутой гугления, не из головы взял

С MobX открываешь документацию, потом 3.5 примера и все

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

А $ в названиях переменных так это вообще вишенка на торте

Это рекомендации, как и название интерфейсов или представлений компонент и тд.

Напомню, что в начале статьи написал следующее (Не было цели сравнивать что-то, цель - ознакомительный характер, а ваши комментарии начинают нести придирчивый характер):

Часть №1 будет нести ознакомительный характер c инструментом, чтобы вы могли понять, нужен ли вам Effector или нет...

С пониманием подхода на MobX все очень просто: любой наблюдатель подписывается на те свойства, к которым обращается. И он подписывается строго сам, никакие там дочерние или родительские компоненты автоматически никуда не подписываются.


Соответственно, чтобы всё работало, нужно помнить следующее: любое обращение к реактивному свойству должно происходить строго внутри computed, autorun, reaction или observer. Если нужно прочитать значения из реактивной модели где-то еще — значит, надо или добавить autorun/observer, или же "доверить" чтение кому-то ещё. Видимо, потому вы и говорите об "обмазывании" toJS.


Но я бы не назвал это фразой "трястить над данными" — нужно просто понимать, вы читаете и передаёте изменяемые данные или неизменяемые. Если у вас есть изменяемая модель, а получателю нужна неизменяемая — да, надо сделать преобразование. Это, в конце концов, вопрос типизации (жаль только Typescript так не считает и позволяет их смешивать).

любое обращение к реактивному свойству должно происходить строго внутри computed, autorun, reaction или observer.

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

"Обратная" ей опция reactionRequiresObservable в общем полезна, но иногда может ругаться зря, когда observer-компонент при некоторых условиях таки не читает наблюдаемые.

Нет ни одной задачи во фронтенде, где React + MobX может не справится или может не подойти.

  • Виртуализированный рендеринг сложных интерфейсов.

  • Редактор документов, электронных таблиц и тп вещей.

  • Растровая и 3д графика.

  • Даже просто создание кастомизируемой библиотеки виджетов - та ещё боль на Реакте.

Растровая и 3д графика.

React Native? Мне казалось что он как раз чем-то таким и занимается.

Для canvas https://habr.com/ru/post/276585/

В браузере для плавных анимаций ещё нужна синхронизация с requestAnimationFrame. По сути это back pressure - когда продюсер изменений подстраивается под скорость их обработки получателем.

Это всё из оперы "троллейбус из буханки хлеба". Можете сами скачать, например, приложение Самоката на RN и полюбоваться тормозящими анимациями с задержкой в несколько секунд.

Mobx это умный стор для тупых клиентов. Redux-like предлагают наоборот - примитивный стор для умных.

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

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

MobX - это не стор, а реактивная система, связывающая состояния. Стор на MobX - это MST.

MST это state - компонент стора. Но спасибо за коммент, терминология это тоже важно.

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

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

Мда, вот это "логика", тяжелый случай.

Единственное, в разработке требуется дисциплина (лучше что-нибудь из области математики)

Ага, а ещё лучше из ядерной физики в перемешку с астрологией и битвой экстрасенсов.

Но развивая подход, вся сложность оказывается сконцентрированной в одном месте и оно становится узким

Это где? В вашем выдуманном мире? Если вы сами ее сконцентрируете в одном месте, она там будет, если нет, то нет. Можете размазать, можете не размазать и т.п. Это вы пишете код, а не mobx, effector, redux и т.п. Как вы сами напишете, ровно так и будет. Отсюда все "беды", во всем в первую очередь виноваты кривые ручки, а во вторую уже выбранные технологии и подходы.

Effector предоставляет неплохой набор инструментов для такой композиции.

Увы и ах, нет.

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

Если вовремя вспомнить про декомпозицию — то нет, ни разу не становится. Надо лишь перестать притворяться, что SOLID — это что-то страшное энтерпрайзное, и что на фронте он не нужен. Нужен, как и куча других аббревиатур.


Нет ни единой причины сваливать всю логику ни в один класс, ни в один стор.

Если ваша цель запороть проект с самого начала, то да, возьмите Effector

С таким же успехом можно "запороть" проект redux'ом. Или mobx'ом. Или чем угодно. Так что я бы не стал так уж в лоб говорить о "запороть"(эффектором не пользовался, если что)

Легковесность и скорость.

https://github.com/artalar/reactive-computed-bench

Больше никакого бойлерплейт-кода.

//api.js

export const getCount = (payload) => {
  return axios.get('/count', payload)
}

//init.js

export const getCountFx = createEffect(getCount)

export const $count = createStore(0)
  .on(getCountFx.doneData, (_store, res) => res.data.count)

//components.jsx

export Component = () => {
  const count = useStore($count)  

  if (getCountFx.pending) {
    return <h1>Loading...</h1>
  }

  if (getCountFx.failData) {
    return <h1>Error</h1>
  }

  return (
    <h1>{ count }</h1>
  )
}

vs

//api.js
export class API extends Object {
  @mem count() { return fetchJSON( '/count' ).data.count }
}

//counter.jsx
export class Counter extends Component< Counter > {
  count(){ return API.count() }
  vdom() { return <h1>{ this.count() }</h1> }
}

все это перекрывается прекрасным комьюнити, которое всегда готово прийти на помощь, например, в tg канале

Или забанить, если будешь задавать глупые вопросы.

Больше никакого бойлерплейт-кода.

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

Redux

//actions.ts - Определить экшен

const someAction = function (props) {
  return {
    type: "SOME",
    props
  }
};

//reducer.ts - обработать экшен и изменить стор

const reducer = function(state, action) {
  switch (action.type) {
    case "SOME":
        return ...
  }
  return state;
}

//Component.tsx

const Component = () => {
  const dispatch = useDispatch()

  const handleSome = () => {dispatch(someAction())}

  return (...)
}

vs Effector

//init.ts

const someAction = createEvent()

const $someStore = createStore()
                      .on(someAction, (state) => state)

const Component = () => {

  const handleSome = () => {someAction}

  return (...)
}

И это мы в редасе еще стор сам не создавали, initialState не описывали и тд )
Думаю, вы меня поняли.

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

Спасибо за комментарий.

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

Больше никакого бойлерплейт-кода.

Смотрю в код тестов по вашей ссылке

Effector

const entry = createEvent<number>()
const a = createStore(0).on(entry, (state, v) => v)
const b = a.map((a) => a + 1)
const c = a.map((a) => a + 1)
const d = combine(b, c, (b, c) => b + c)
const e = d.map((d) => d + 1)
const f = combine(d, e, (d, e) => d + e)
const g = combine(d, e, (d, e) => d + e)
const h = combine(f, g, (h1, h2) => h1 + h2)

mol

const entry = new $mol_wire_atom('entry', (next: number = 0) => next)
const a = new $mol_wire_atom('mA', () => entry.sync())
const b = new $mol_wire_atom('mB', () => a.sync() + 1)
const c = new $mol_wire_atom('mC', () => a.sync() + 1)
const d = new $mol_wire_atom('mD', () => b.sync() + c.sync())
const e = new $mol_wire_atom('mE', () => d.sync() + 1)
const f = new $mol_wire_atom('mF', () => d.sync() + e.sync())
const g = new $mol_wire_atom('mG', () => d.sync() + e.sync())
const h = new $mol_wire_atom('mH', () => f.sync() + g.sync())

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

Если что, на $mol так не пишут. $mol_wire_atom - низкоуровневая абстракция, эквивалентная остальным решениям в этом бенчмарке. Типовой код на $mol_wire можно глянуть, например, тут.

Если что, на $mol так не пишут.

Ну и какая польза от такого бенчмарка? Если оторванные от реальности способы допустимы, то нарисовать линию на графике можно гораздо проще.

Кстати, а как там должно быть написано, чтобы это стало похоже на правду?

Да никакой. Бенчмарк этот довольно далёк от реальности. Там замеряется кейс вида "есть сложный DAG с тривиальными формулами, и на каждый чих они все пересчитываются". Это худший случай для $mol_wire и MobX и лучший для Effector и S.js. Но даже в этом случае, эффектору не удаётся конкурировать, в отличие от S.js. На более-менее реалистичном кейсе с кучей данных, тяжёлыми вычислениями и динамическими зависимостями эффектор просто встанет колом.

Вот, кстати, более свежие результаты на моей машинке:

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

Спасибо, теперь выглядит понятнее.

взаимный прогрев одной библиотеки

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

Там два разных API к одной библиотеке. Использование одного прогревает и другой. Аналогичная проблема есть и при использовании общей зависимости разными библиотеками. Поэтому адекватно замерить непрогретую библиотеку можно разве что перезапуская ноду каждый раз. Хотя, смысла замерять холодные либы не очень много. Значение имеет обычно горячий код.

Не могли бы вы объяснить, каких попугаев меряет бенчмарк по оси Y? От дополнительной информации касательно оси X тоже не откажусь

Это бестолковые "бенчмарки", не относящиеся к реальным проектам и реальной логики приложений. Цикл "for in" быстрее чем цикл "for of", но в реальных клиентских вэб приложениях вы никогда не заметите экономии в микросекнудах и наносекундах. Тут нужно опираться только на то, какой код вам позволяет писать та или иная библиотека. Это важнее на порядке, чем то, на сколько надо секунд X обгонит Y. Тем более речь о клиентском приложении, а не о сервере.

По Y отставание от лидера (100%). Сомнительная метрика, конечно.

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

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

Всё-таки выборка для оценки должна быть побольше, например три проекта на мобх, и три проекта на эффекторе. При чём не так, что начинал с нуля, а потом уволился (потому что говорят что надо "развиваться" и прыгать с места на место). А наоборот, чтоб пришёл на проекты уже которые в возрасте.

Мне достался двухлетний проект на mobx-е, он большой (при чём не вплане формочек, там только одна форма на входе, а вплане накрученной логики, там и sip + web-rtc, и electron). Всё супер понятно, уже четвёртый год пошёл проекту, а он развивается быстрее всех, с той же скростью что и была в начале, при этом без увеличения энтропии и wtf-индекса.. вся сложность упразднена. Порой такие требования прилетают, что кажется если бы не mobx - проект бы уже переписывали или закрыли. И самое классное - ни одного простигосподи usecallback-а, useMemo, memo в реакте. И только один реактовский контекст на всё приложение. Реакт чисто как шаблонизатор. Вобщем очень удобно.

Мне достался двухлетний проект на mobx-е, он большой (при чём не вплане формочек, там только одна форма на входе, а вплане накрученной логики, там и sip + web-rtc, и electron). Всё супер понятно, уже четвёртый год пошёл проекту, а он развивается быстрее всех, с той же скростью что и была в начале

Всё правильно, так и должно быть, вот что бывает когда адекватные люди разрабатывают и во главе всего стоит "KISS (Keep it simple, stupid)YAGNI (You aren't gonna need it)Чем меньше, тем лучше (Less is more)".

Порой такие требования прилетают, что кажется если бы не mobx - проект бы уже переписывали или закрыли

Так и случается в 100% случаев)

Все от сложности проекта зависит, верно подметили.

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

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

Всё-таки выборка для оценки должна быть побольше, например три проекта на мобх, и три проекта на эффекторе. При чём не так, что начинал с нуля, а потом уволился (потому что говорят что надо "развиваться" и прыгать с места на место). А наоборот, чтоб пришёл на проекты уже которые в возрасте.

Соглашусь.
У меня с МобХ первый проект сейчас, поэтому никакой конкретики не приводил, плевки в сторону других решений не делал. Вкатился быстро, да так же как и можно вкатиться в проект на эффекторе (если рядом тебя за ухо поднатаскают), единственное отличие - у эффектора инструментарий будет пошире (и в ssr и в байндинг пропсов (никаких тебе вызовов юзстора в компонентах), ну и тд, следующую статью по инструменталу как раз готовлю), в этом и сложность для многих.

Спасибо за комментарий )

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

import { observer } from 'mobx-react';
import { Component } from 'react';

import { TypeGlobals } from 'models';

import { StoreContext } from './StoreContext';

export class ConnectedComponent<TProps = any, TRoute = never> extends Component<TProps, any> {
  // SSR not able to recalculate components on observable updates,
  // so on server side it has to be rendered twice
  static observer = IS_CLIENT ? observer : (someComponent: any) => someComponent;

  // Describe context, so no boilerplate in components needed
  static context: TypeGlobals;
  static contextType = StoreContext;
  declare context: TypeGlobals;
}

// Any component

class AnyComponent extends ConnectedComponent<TypeProps> {
  render() {
    this.context // Perfectly typed and reactive, no boilerplate at all
    this.props // Perfectly typed and reactive, no boilerplate at all
  }
}

В Webpack делаю транформер, который все компоненты, которые экстендят ConnectedComponent превращаются в

const AnyComponent = ConnectedComponent.observer(class AnyComponent...)

Так получается реактивность без сахарных декораторов (чтобы использовать не Babel, а более шустрый SWC транспайлер), с отдельной логикой для SSR и без бойлерплейта.

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

Автор, спасибо за статью! Сейчас работаю в проекте где впервые столкнулся с Effector-ом, и вы мне очень помогли разобраться

Sign up to leave a comment.

Articles