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

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

Getters / Setters - не, не слышал. Буду делать так, чтобы пользоваться было максимально неудобно.

До MobX'a конечно ещё очень и очень далеко.

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

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

С чего это вдруг? Неправда.

async doSome() {
  this.fetching = true;
  this.doSomeCallsCount++; 
  
  try {
    const response = await apiReq('GET /alala');
    this.items = response.items;
    this.error = null;
  } catch (e) {
    this.error = e.message;
  } finally {
    this.fetching = false;
  }
}

Где тут избыточность и сложность? Геттеры и сеттеры под капотом, они не используются при написании кода.

они менее явные и я всегда стараюсь их избегать

Взгляните на пример с кодом выше, в каком месте вы видите хоть грамм не явности?

Дебаг мутабельных данных намного сложнее

Да ладно? А может вы в блокноте код пишете?)

Webstorm:
Клик правой кнопкой -> Find Usages


VS Code:
Клик правой кнопкой -> Find All References

Итого: за 5 секунд нашли все места где читается и где изменяется нужная нам переменная, если сразу не понятна причина бага, то добавляем console.log в нужные места и вуаля, мы элементарно нашли причину бага.

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

А да? Т.е. React + React-DOM размером в более чем 150kb это норм, а MobX добавляющий ещё пару копеек, это уже проблема? Не смешите. Или вы всё ещё в 2010 году живете?

но и перф у него до двух раз хуже

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

Очень специфическое решение, этот ваш мобыкс

Вообще кошмар :D

И экосистема у него не большая и не развивается

Зачем экосистема полностью готовому, самодостаточному, законченному и работающему проекту? Просто бери его и вперед.

Есть такая профессия – MobX любить.

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

С чего это вдруг? Неправда.

Нельзя добавить логики и не уменьшить семантику, это база в теории ЯП и инженерии. Мобыкс предполагает скрытое добавление семантики с которой нужно жить и учитывать: присвоение проперти в переменной не делает ее реактивной, геттеры в форыче могут заметно повлиять на перф, и в общем где-то можно не намеренно подписываться на то что не нужно, например ридонли id для `key` в рендеринге списка элементов.

Это не катастрофическая проблема, просто, по моему мнению, избыточная.

Где тут избыточность и сложность?

Совсем печально что вы не знаете ответ на этот вопрос. Мобыкс, в зависимости от настроек, может не забатчить несколько последовательных апдейтов после авейта.

пример с кодом выше

Зачем мне этот пример? В реальном приложении все сложнее. Кстати, в мобыксе нет сущности event / action и нельзя построить событийную модель, а иногда надо. Например, как вы будете отправлять лог аналитике при клике по кнопке? Засунете этот код прям в хендлер? Грязно, грязно!

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

150kb это норм

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

Зачем экосистема

Ну тот же @reatom/async очень удобен, в мобыксе такого не хватает. А еще у нас почти готовы пакеты для офлайн синхронизации и форм, позже будет пакет для роутинга. Намного лучше когда это все разрабатывается одной группой лиц.

Нельзя добавить логики и не уменьшить семантику, это база в теории ЯП и инженерии. Мобыкс предполагает скрытое добавление семантики с которой нужно жить и учитывать: присвоение проперти в переменной не делает ее реактивной, геттеры в форыче могут заметно повлиять на перф, и в общем где-то можно не намеренно подписываться на то что не нужно, например ридонли id для `key` в рендеринге списка элементов.

Это не катастрофическая проблема, просто, по моему мнению, избыточная.

Бла бла бла, много воды и всё не по делу. Только 1 "аргумент", который легко проверить.

Вот он:

геттеры в форыче могут заметно повлиять на перф

Проверяем:

class Test {
    count = 1;

    get c() {
        return this.count;
    }
}

const test = new Test();

let result = 0;
console.time('t');
for (let i = 0; i < 100000; i++) {
    result += test.c;
}
console.timeEnd('t');
console.log(result);
О ужас, 1ms в сто тысяч итерацией и по мимо get тут 2 раза идет инкремент в каждой итерации
О ужас, 1ms в сто тысяч итерацией и по мимо get тут 2 раза идет инкремент в каждой итерации

Вывод: Ваш "аргумент" это просто пустые слова не подтвержденные практикой и реальными проектами и задачами.

Совсем печально что вы не знаете ответ на этот вопрос. Мобыкс, в зависимости от настроек, может не забатчить несколько последовательных апдейтов после авейта.

Да-да, конечно же я ничего не знаю и вообще первый день в разработке.

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);
    },
});

Ой, а вот и автобатчинг.

Зачем мне этот пример? В реальном приложении все сложнее. Кстати, в мобыксе нет сущности event / action и нельзя построить событийную модель, а иногда надо. Например, как вы будете отправлять лог аналитике при клике по кнопке? Засунете этот код прям в хендлер? Грязно, грязно!

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

А грязно это вот этот ваш лютый говнокод:

export const Greeting = () => {
  const [input, setInput, inputAtom] = useAtom('')
  const [greeting] = useAtom((ctx) => `Hello, ${ctx.spy(inputAtom)}!`, [inputAtom])

  return (
    <>
      <input value={input} onChange={e => setInput(e.currentTarget.value)} />
      {greeting}
    </>
  )
}

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

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

Ну вам норм, но не все на реакте пишут

Так пусть пишут, мне то что с того.

Ну тот же @reatom/async очень удобен

Вообще нет, это просто очередная лапша. А все async хэлперы реализуются элементарно с помощью MobX.

И вообще взрослые дядьки пишут плагины и трансформеры, чтобы оставлять исходный код красивым и чистым, а на этапе "компиляции" уже его модифицировать как нужно, например заворачивать компоненты в observer'ы, например разруливать race condition'ы, debounce'ы и т.д и т.п. и при этом чтобы не приходилось писать убогие конструкции и лапшу. А просто бизнес логику сверху вниз, слева на право.

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

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

Вот вам прямо с MobX'ом

const { makeAutoObservable } = require('mobx');

class Test {
    count = 1;

    constructor() {
        makeAutoObservable(this);
    }
}

const test = new Test();

let result = 0;
console.time('t');
for (let i = 0; i < 100000; i++) {
    result += test.count;
}
console.timeEnd('t');
console.log(result);
Кошмар, 6ms вместо 1ms на 100тыс итераций
Кошмар, 6ms вместо 1ms на 100тыс итераций

Например, как вы будете отправлять лог аналитике при клике по кнопке? Засунете этот код прям в хендлер? Грязно, грязно!

Как это бестпрактисно выглядит на Реатоме?

Ну тот же reatom/async очень удобен, в мобыксе такого не хватает. 

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

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

Те модуль аналитики просто импортирует нужные атомы / экшены и подписывается на них. В данном примере это будет так

```javascript
// anal.ts
import { onUpdate } from '@reatom/hooks'
import { onSomeClick } from '~/features/some/model'

onUpdate(onSomeClick, () => log('onSomeClick'))
```

Я так делал, вполне удобно, код фичи становится заметно чище.

Это наивное решение. Есть еще возможность собирать логи автоматически - подписаться вообще на все логи через `ctx.subscribe((logs, error) => logs.forEach((patch) => patch.proto.name && log(patch.proto.name, patch.state)))`, но тут нужно побольше фильтров наставить каких-то, конечно.

  • При реализации фичи придётся править ещё и код аналитики в совсем другом месте.

  • Аналитика потянет в бандл все фичи, даже те, что не используются.

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

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

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

Потому, что она их все импортирует к себе.

Есть еще возможность собирать логи автоматически - подписаться вообще на все логи через ctx.subscribe((logs, error) =&gt; logs.forEach((patch) =&gt; patch.proto.name &amp;&amp; log(patch.proto.name, patch.state))),

https://mobx.js.org/analyzing-reactivity.html#spy

Там же, кстати, и для дебага тулзы

ну вот проблема в том что в мобыксе все равно нет эвент-лайк сущности и сам клик по кнопке в мобыксе никак не отметится. Реатом, с моей точки зрения, в этом плане лучше архитектуру навязывает.

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

Эм, а у одних и тех же кнопок будет один и тот же хендлер?? Если логику строить на определении атрибутов в event.target - да, но таким редко кто страдает (и зачем?).

В любом случае, в модуле аналитике лог будет выглядеть так `onUpdate(onClick, (ctx, { params: [event] }) => log('click', event.target.dataSome))`

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

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

Где тут избыточность и сложность?

Сравните с этим:

@mem doSome() {
  this.doSomeCallsCount++
  return apiReq('GET /alala').items
}

а где в вашем варианте обработка ошибок?

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

А как именно обрабатываются?

Что делать, если в зависимости от ошибки нужно показать какое-то сообщение пользователю?

Если надо что-то кастомное, то заворачиваем в try-catch и делаем всё, что захотим:

@mem pageTitle() {
  
  try {
    return this.task().title()
  } catch( cause ) {
    
    if( cause instanceof Promise ) {
      return `Task #${ this.task().id() }`
    }
    
    if( cause instanceof HttpError ) {
      return {
        2: `?`,
        3: `?`,
        4: `?`,
      }[ Math.floor( cause.code / 100 ) ] || `?`
    }
    
    throw cause
  }
  
}

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

Это как?

Сравнивали с Recoil?

Не интересно, тк он прибит к реакту. Если это не смущает, лучше на jotai посмотреть - проще и легче рекоила.

Для кого-то перф не самое главное

Там и без перфа проблем хватает.

Рад видеть развитие у Reatom. Продолжай!

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

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

  • Хотелось бы интересных примеров более серьезных приложений, где используется ваш подход. Очевидно, что приведенные в статье примеры, ммм, не слишком впечатляют. Хотелось бы лучших практик построения моделей, взаимодействия и так далее. У вас же должно быть видение? Для редакса и мобикса это все есть.

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

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

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

Примеры больших проектов я считаю смотреть безсмысленно, у каждого своя специфика. Но вот в опенсурсе есть один такой: https://github.com/konturio/disaster-ninja-fe

Хм, а можно примеры таких компаний? Я хабр читаю давно, а вот про reatom услышал только недавно. Тот же моль уже здесь всем оскомину набил давно.

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

За последние несколько лет вот эти компании точно использовали реатом: Яндекс, A3, sravni.ru, consta.design, и еще пачка о которых я не помню / не знаю. У самого сейчас reatom/async используется на проекте в пол ляма строк.

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

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

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

К чему приводит пресловутая атомарность селекторов можно наблюдать в этом примере: когда гуляешь по карте довольно скоро всё приложение замирает и перестаёт реагировать на пользователя вообще. А ведь год назад я объяснял почему такое поведение плохо. Особую пикантность ситуации придаёт отсутствие ошибок в консоли.

Причем тут атомарность вообще не ясно. В любом случае, у меня ничего не замирает и потребление памяти вообще не меняется, "гулял" 1 минуту. Как воспроизвести проблему?

Поправил ссылку.

Так что с ней делать?

Открыть и увидеть зависшее приложение через секунду использования.

Не получается (( все работает, хотя жрет пол гига ОЗУ.

К сожалению, действительно виснет. Win 11, Firefox 108.0.1. Можно немного продвинуться по лабиринту, а потом всё.

stackblitz возможно что-то закешировал слишком агрессивно. Возможно поможет очистка локального хранилища.

Спасибо, за отличный пример. Пометил себе реатом в мертвооожденные. Обожаю хабр за это, один на серьёзных щах пердлагает ганвокодить по его идеологии, а комьюнити просто показывает, что поделка не работает и желает счастливой отладки! Лучше аргумента и не придумать, браво)

Пометил себе реатом в мертвооожденные

Полностью поддерживаю

Обожаю хабр за это, один на серьёзных щах пердлагает ганвокодить по его идеологии

Во во, и самое смешное и печальное что остальные этот бред подхватывают

Вы же потом отпишите – в чем баг то был!

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

В общем-то нашел, понятно – в $playerBlock индекс вне границ будет.
Энивей немного странно слышать комбинацию из "чистой функции" и "npe". Надеюсь курсивом не просто так выделено.

А что предлагают другие решения в таких ситуациях?

Понятно.

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

Обратил внимание на ctx.schedule(), но не уверен, что это оно.

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

schedule нужен что бы планировать сайд-эффекты из экшенов.

У меня тоже замирает. Секунд через 5.

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

Мобх умеет так делать даже в реактовских компонентах. А вот Реатом, кажется, нет: useAtom подписывается безусловно.

Это хорошее замечание, реатома пока так не умеет делать внутри компонента, но я работаю над этим (не думал что эта фича приоритетная).

А в чем мотивация иметь отдельные first class citizen get и spy?

Это создает ситуацию, когда очень легко сделать get, не подписаться и потерять реактивность. И из-за относительной похожести визуально и того, что в большинстве других либ реактивности чтение = подписка, на код ревью такое тоже легко пропустить.

Да не, через несколько дней / неделю привыкаешь и становится очень странно видеть spy в экшенах и get в атомах, сразу за это глаз цепляется. Они как раз удобно что всегда рядом и оба на 3 буквы, но сами буквы очень уж отличаются, как и произношение (в голове когда код читаешь).

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