Так и что в итоге произойдет, если я вызову postData() где-то за пределами реактивного контекста? Он выкенет промис, который потом в итоге отменится? Или вернет результат работы getJSON(url2)? Я ожидаю, что:
либо изменение this.flag приведет к инвалидации postData() и, соответственно, отмене/реджекту выкинутого им промиса;
либо изменение this.flag приведет к инвалидации postData(), в результате чего он будет перезапущен, но выкинутый промис будет переиспользован и я в итоге получу значение getJSON(url2);
либо после завершения работы getJSON(url1) компьютед будет вызван еще раз, но т.к. this.flag изменился, выполнение пойдет по второму пути, запустится getJSON(url2) и уже после его завершения я получу его результат.
А что помешает мне вызвать postData() вне реактивного контекста? Вы же выше писали, что такое возможно и в этом случае мне нужно будет самостоятельно поймать промис и дождаться его выполнения?
Еще один вопрос: а обрабатываете ли как-то ситуации, когда в процессе выполнения компьютеда вне реактивного контекста какая-то из его зависимостей меняется? Что-то вроде такого:
class Store {
$mems postData() {
// this.flag - реактивное значение
return (this.flag) ? getJSON(url1) : getJSON(url2);
}
}
В данном примере что вернет postData() вызванный вне реактивного контекста, если значение this.flag изменится до завершения вызова getJSON()?
Upd: заглянул в реализацию и оказалось, что для того чтобы "магия" автообновления работала, библиотека патчит React и этот функционал не будет работать в React 19.
Мне нравится концепт сигналов, но такое решение не кажется хорошей идеей.
Штож, никто и не говорит, что все решения принятые командой React идеальные.
Я так понимаю тут речь про хранение локального состояния и функций в mobx.
Тут речь о том, что проблем с HOC'ами в MobX нет. Конкретно относительно Ваших тезисов хотя бы потому, что MobX не предлагает создавать HOC'и, он лишь предоставляет один для превращения обычных компонентов в реактивные. Это как memo() в React, который делает компонент чистым.
Но технически - да, можно и локальным состоянием прекрасно управлять без большей части хуков. Получается очень приятный такой MVVM. На всякий случай уточню, что речь про бизнес логику.
Вот тут с удовольствием бы поспорил предметно, с примерами.
Выше по комментариям были разные примеры кода на MobX. Если их не достаточно - придумайте задачку и возможно мы с Вами сравним решения.
Кстати, по поводу HOC'ов - они не обязательно обязаны создавать дополнительную ноду в React дереве)
Вы ошибаетесь. Именно классы - по сути раннее связывание данных и методов (не интерфейса и методов), и наследованные - и есть ООП.
Даже в самом названии ООП нет слова "классы", речь об объектах. А наследование - лишь один из принципов. Можем хоть к Википедии обратиться. Ну или кидайте свои более правильные ссылки)
Скорей даю подсказку
Да не, апеллируете к авторитету =)
А язык Go как раз и создавали, чтобы уйти от проблем ООП языков, и успешно с этим справились - то есть над этим точно задумывались
Откуда информация? У Вас в предложении отдельные части не согласуются.
Как и тех кто делал React - эти сами прошли по всем граблям, и каждый сегодня может сам сравнить ООП vs функции
А разве на функции перешли не потому, что они лучше ложились конкретно на концепцию файберов (это не точно, ссылку искать лень).
Думаю никто в здравом уме сегодня не выберет классы как основной способ создания компонент.
Еще раз напомню, что кроме React есть другие инструменты, например Angular, в котором на классах все прекрасно организовано.
А в музыкальных трендах вашей соцсети откровенное 💩, и?) Мне бросить Pink Floyd, Кино, ZZ Top и мн. др. и пойти слушать нашу эстраду?
Субъективное суждение.
Не понимаю, как оно относится к моему тезису про то, что Java прекрасно живет с ООП и никто там даже не пытается от него убежать. Выглядит как эмоциональный выброс.
Вы руководствуетесь в суждениях хайпом
предсказываю что скоро будет что ООП - отстой
Выше в другом комментарии я писал, что мне кажется что текущая ФЕ повестка как раз давно уже звучит как "ООП - отстой". Лично я каждый раз спрашиваю того, кто так заявляет в чем причина такого мнения. Еще ни разу не получил конкретного аргумента. Ждем Вашу статью.
Чем лучше технология, тем она проще, понятнее, и в ней меньше "магии" (KISS)
Вы немного не так поняли мой тезис. Я имел в виду, что понятие "магия" как будто зависит от того, кто его применяет. Я специально привел Вам примеры случаев, когда люди могут не вдаваться в подробности устройства тех или иных техник, и тогда они превращаются для них в магию. Стоит в этом разобраться (даже поверхностно) - и вся "магия" уступает место логичному и предсказуемому поведению.
Когда то и классы были чуждой концепцией.
Новой, но не чуждой. Мой тезис был про то, что хуки - априори не новый подход, а костыль.
разрабы React моментально пересели на функции с хуками, и возвращаться даже не думают.
Вы тут имеете в виду непосредственно разработчиков библиотеки, или тех кто ей пользуется? Мотивы первых мне не известны, а вторые думаю ясно почему "моментально пересели".
Кстати, Вы же знаете, что функции до сих пор не могут полностью заменить классы?
Забавно, что здесь "магию" вы уже не защищаете.
Это я не понял, про что Вы. На всякий случай поясню - я не за/против магии, я лишь говорю, что "магия" = "я не понимаю как оно там работает" и этот принцип для разных людей работает на разных масштабах. Как Event Loop - кто-то знает как он работает в деталях, а кому-то это не интересно.
При этом - их можно использовать в нескольких разных компонентах, но компоненты будут прибиты к этим хукам намертво. В то время как компонент можно завернуть в разных HOC'и без вмешательства в его нутро.
Да что вы говорите. Тот же connect у redux наверное очень плохой, как же его переписать чтоб ничего не прокидывал
О чем тут речь? Что Вы там прокидываете из одного HOC'а в другой?
В современных языках типа Go и фреймворках типа React (да это фреймворк) от ООП отказались не просто так.
Насколько мне известно, в Go не отказались от OOP. Там есть структуры и множество способов выстраивать отношения между ними. Наследование - не единственный признак ООП;
В данном случае вы апеллируете к авторитету. С тем же успехом можно сказать, что "в современных языках типа Rust и фреймворках типа Angular от ООП не отказались не просто так";
И создатель Java признавал что зря добавил классы
Тем не менее одна из самых популярных платформ и большинство инструментов и библиотек в ней используют именно ОО подход и паттерны.
Я не говорю, что ООП лучше ФП, но и заявлять обратное тоже считаю некорректным. У каждого подхода свои плюсы/минусы и область применения.
Магия это как раз совершенно неочевидные большинству разработчиков, непривычные вещи типа makeAutoObservable и пр.
Любая более-менее сложная технология неотличима от магии. Если не вдаваться в принципы работы используемого инструмента, то даже самый простой может работать как магия и подкидывать сюрпризы где их не ждешь. Пара примеров:
Выше в комментариях мы обсуждали сигналы, и там тоже есть "магия" обновления нативных нод, которая при незнании может обернуться тем, что компонент начнет обновляться частично.
Тот же контекст, который Вы упоминаете, при неправильном использовании приводит к большому количеству лишних обновлений UI. Если попробуете погуглить, то можете даже обнаружить что многие люди в принципе считают контекст плохой практикой, "потому что он приводит к обновлению всего и вся" (на самом деле это не так).
По поводу HOC - они
Интересно, но когда React был на классах - HOC'и были крутым паттерном переиспользования логики, но как-то со временем все извратилось и сейчас то же самое говорят про хуки, хотя на самом деле:
Хуки - совершенно чуждая любому здравомыслящему разработчику концепция, просто создатели React не придумали ничего лучше для функциональных компонентов. Попробуйте вспомнить использование подобного подхода где-то еще.
Хуки позволяют переиспользовать логику ровно в той же мере, что и обычные утилитарные функции. Вот только любой компонент на хуках намертво к ним прибит, в то время как логика HOC полностью отделена от логики компонента и они оба могут быть использованы независимо.
Используя хуки useEffect(), useCallback() и useMemo() вы порождаете новую функцию (и замыкание, которое кстати дороже класса) на каждый рендер, даже если она в итоге не будет использована. Это дополнительно нагружает GC. Лично я не стану заявлять, что лишние статичные ноды в React-дереве бьют по производительности сильнее.
Более того, проверка зависимостей этими хуками не бесплатна (как и внутренняя логика их вызова в React) и происходит каждый рендер. Именно поэтому использование useMemo() и useCallback() получается таким противоречивым - вроде по логике нужно заворачивать в них все, что потом попадает в пропы дочерних компонентов, а вроде неизвестно - мемоизированы ли дочерние компоненты и будет ли это иметь смысл?
Использование хуков увеличивает функцию рендера, задача которой преобразовать входные данные и состояние в UI. Вместо этого в той же функции мы описываем инициализацию, эффекты и деструктуризацию. Очень странно слышать, что функция перегруженная ответственностью читается легче, чем несколько отдельных самостоятельных функций.
Если Вы прокидываете данные между HOC'ами, вы скорее всего делаете что-то неправильно (например, разделяете то, что не должно быть разделено). То, что Вам приходится передавать значения между хуками - это как раз следствие того, что на этот костыль наложили слишком много ответственности. Например, useMemo() и useCallback() можно заменить простым memoize-one , который кстати можно вызывать в условиях и циклах.
Но это все абстрактно. Если говорить предметно, про тот же MobX - там существует один единственный HOC, у которого отсутствуют описанные Васм проблемы и его использование позволяет отказаться от большей части хуков (особенно от самых неудачных), сделав код сильно более читаемым.
Посмотрел - судя по всему сигнал выдает себя за React элемент, что позволяет его срендерить в JSX. Интересная оптимизация, но к сожалению работает только при использовании в качестве контента. Если использовать этот же сигнал в выражении или в качестве свойства - работать не будет =(
А вот чтобы обновлялся второй элемент, придется использовать useSignals(), но в этом случае оптимизация просто перестанет работать (сообщения начнут сыпаться в консоль).
На моем опыте - если библиотека предоставляет несколько вариантов поведения, зачастую команда останавливается на каком-то одном, чтобы не нарваться на неожиданности при рефакторинге. Поэтому, могу предположить, что в реальном проекте принято во всех компонентах использующих сигналы всегда добавлять useSignals().
Он перерисовался, как бы это сказать...без ведома реакта) даже профайлер не покажет перерисовку
Как это без ведома - посмотрите в консоль, там будет вывод "render" на каждый рендер.
Более того, вы можете сделать к примеру так
Ну так в данном случае вы не используете значение в основном компоненте, но дочерние будут рендериться каждый раз при изменении. Тут ничего удивительного. Грубо говоря, любой менеджер состояния так себя поведет, просто в одних за использование считается непосредственное чтение (MobX, Reatom, Recoil и тд), а в других - использование хука (Redux, Effector и тд).
Здравствуйте. Как написано в статье - большинство критериев оценивалось поверхностно, на основании данных с официальных сайтов библиотек и нашей интуиции. Так, например, авторы эффектора часто заявляют, что их библиотека производительнее, чем Redux и другие конкуренты - вот мы и поверили на слово. Вы сами написали, что вокруг Redux витает много мифов. Эти мифы и стереотипы в нашем случае тоже повлияли на оценку.
По поводу бенчмарков добавлю: достаточно сложно придумать какой-то хороший пример, который бы закрывал все потребности в инструменте и одновременно хорошо подходил для бенчмарканья (чтобы в нем и списки были, и асинхронные действия, и связь между модулями и т.д.). Поэтому во второй раз мы их тоже не делали, а отдельно рассматривали конкретные аспекты работы инструментов, важные для нас, о которых написано в статье.
есть мнение, что все что сделано на классах по умолчанию плохо спроектировано
Присоединюсь к другим комментаторам и с удовольствием послушаю аргументы в пользу этого тезиса. Скиньте ссылку на статью, когда будет готова.
Функциональный стиль не является плюсом - это просто инструмент, такой же как и ООП, иммутабельность, чистые функции и многое другое. Он позволяет за счет определенных ограничений получить конкретные преимущества в конкретной ситуации, но сам по себе не является преимуществом или недостатком.
...отсутствие магии и оберток HOC
А еще подскажите, что в Вашем понимании "магия"? И почему она и HOC'и это плохо?
Я не эксперт по сигналам преакта, но кажется что чтобы отделные нативные ноды могли быть реактивными, нужна какая-то логика в самой библиотеке (как, например, у spring есть свои версии нативных элементов) или трансформация при сборке. Решил глубоко не копать, а просто проверить ваше предложение на практике (песочница), и там ожидаемо обновляется весь компонент - можете сами проверить. Я ничего не упускаю?
Cигналы из Preact действительно крутая штука. Я полагаю они не рассматривались по нескольким причинам.
Во-первых, задолго до внедрения эффектора у нас был эксперимент по использованию Preact, но он не взлетел из-за плохой совместимости с некоторыми внутренними решениями, которые уже существовали на тот момент. Полагаю, что этот неудачный эксперимент и то, что сигналы обычно не рассматриваются отдельно от Preact привели к тому, что их не было в табличке сравнения (та же магия обновления нод, о которой Вы упомянули работает только вместе с Preact).
Во-вторых, если сравнивать с MobX, то тут у последнего для нас есть несколько преимуществ:
Объектный стиль получается гораздо лаконичнее - нам не нужно явно писать singnal/computed в подавляющем большинстве случаев, получается обычный JS +минимум бойлерплейта.
MobX все же популярнее и имеет более обширное комьюнити. Мне также не известно крупных проектов, использующих сигналы Preact без самого Preact (тут возможно информация устарела).
Со стороны кажется, что каждый из вас по-своему прав:
В примере есть autorun(), использующий значение реактивной переменной. Благодаря тому, что обновления распространяются синхронно на каждой итерации происходит вызов реакции. Т.е. реактивная система действительно задействована.
Разные СТМ по-разному кэшируют вычисления, что может сильно повлиять на результаты бенчмарка. Полагаю, речь о том, чтобы сделать дерево зависимостей чуть сложнее (добавив больше компьютедов).
Если Вы еще не писали про все эти проблемы в поддержку, то можете скинуть мне в личку чуть более подробное описание (например, что именно в плеере не работает) и шаги для воспроизведения (особенно интересно про фотоальбомы) и я передам информацию ответственным командам.
Добавлю к этому тот факт, что указанные материалы к сожалению отсутствуют в поисковой выдаче по запросу "проблемы effector", что сильно уменьшает вероятность того что они будут учитываться в процессе принятии решения 😔
Получается, если в рамках одного "компьютеда" я вызову одну и ту же псевдо-синхронную функцию дважды с одним и тем же набором аргументов - она вызовется дважды? А если в двух разных "компьютедах"?
А при повторном вызове (после резолва промиса)? Кеширование не привязано к реактивном контексту?
Тоже думал что может быть утилита принимающая на вход массив коллбеков, но надеялся что может быть решение поизящнее.
Т.е. повторный вызов при инвалидации зависимостей и повторный вызов после резолва промиса отличаются. Получается, что при повторном выполнении "компьютеда" даже если все функции внутри будут вызваны в том же порядке и с теми же аргументами - они будут вызваны повторно, верно? В принципе, это имеет смысл.
Ссылки на материалы, которые мы непосредственно использовали в исследованиях или при подготовке статьи указаны по месту (там, где идет соответствующее повествование).
Под "статьей трехлетней давности" Вы имеете в виду ссылку на пост про проблемы Effector? Кмк, возраст поста тут не имеет значения, он скорее призван показать как трудно было во времена нашей истории (да и сейчас) найти любую полезную альтернативную информацию конкретно про Effector.
Такой таблички нет, но некоторые концепции из $mol_wire мы какое-то время обсуждали. Пользуясь случаем задал Вам пару вопросов в комментарии к соответствующему посту тут.
Если API описанное в статье еще актуально, то для создания асинхронных компьютедов все асинхронные функции превращаются в псевдо-синхронные работающие на Suspense API. Пара вопросов по этому поводу:
Чтобы обработать исключения Вы каждый раз в блоке catch вставлять проверку на промис (может есть смысл сделать утилиту типа rethrow()?). Учитывая, что синхронная функция внешне не отличается от псевдо-синхронной - Вы такие проверки делаете всегда во всех catch'ах?
Можно ли использовать одну и ту же функцию внутри одного компьютеда несколько раз? Для каждой нужно создать псевдо-синхронную версию или кеширование данных для каждого набора аргументов происходит на уровне компьютеда?
const getJSON = sync(async function getJSON() { ... });
class Store {
// будет ли такое работать?
$mems postData(id: number) {
const content = getJSON(`/post/${id}`);
const comments = getJSON(`/comments/${id}`);
return { content, comments };
}
}
Если кеширование происходит на уровне компьютеда, то что если я обращусь к нему из-за пределов реактивного контекста?
Псевдо-синхронные функции ждут выполнения друг друга (см. пример выше). Но что если я хочу отправить несколько запросов параллельно? Нужно создавать функцию-обертку и уже ее сделать псевдо-синхронной?
В рамках выполнения компьютеда отличаются ли как-то "перезапуск из-за обновления зависимостей" от "перезапуска после резолва промиса"? Видел в Ваших примерах использование sleep(500) для реализации debounce'а - это тоже псевдо-синхронная функция? Если да, то не значит ли это что задержка сработает только один раз при первом обращении к компьютеду?
Что прям все?) Или 99%?) А если взять самый очевидный пример - Angular, там тоже классов избегают?
Опять же кто?) Да, такие люди есть, но с чего это их мнение правильное?
В данном случае речь о том, что есть такой тренд есть в среде React-разработчиков (к которым мы относимся) и он достаточно популярен . Я ни в коем случае не говорю за абсолютно всех разработчиков.
Angular - очень интересный фреймворк, в разработке собственной архитектуры мы поглядываем в том числе на идеи, которые реализованы в нем и некоторые нам хорошо подходят.
Получается вы попали в классическую ловушку, увидели что где-то кто-то делает вот так, увидели какие-то статьи и всё, ваша картина мира сложилась и вы поняли что да, это именно то, что нужно)))
Примерно так и было. В статье я как раз пишу, что мы выбрали Effector отчасти "на волне хайпа". И это было ошибкой.
Неужели так сложно понять что это гуано не пробуя его на вкус? Например по виду и запаху? Это же сразу очевидно когда смотришь на код эффектора и на этот подход.
Как я упомянул в статье, мы делали выводы из слишком маленького объема информации и простого примера. Грубо говоря, "не почуствовали запах в таком масштабе".
Так в итоге то что? Вот вам дали новый проект на работе, прям с нуля, какой стейт менеджер возьмете к нему?
Мы внедряли Effector во всем ВКонтакте - это наш основной проект =) Внедрением занимались 2 десятка команд целый год. Сейчас переводим на новую архитектуру, которая для управления состоянием использует MobX.
Так и что в итоге произойдет, если я вызову
postData()
где-то за пределами реактивного контекста? Он выкенет промис, который потом в итоге отменится? Или вернет результат работыgetJSON(url2)
? Я ожидаю, что:либо изменение
this.flag
приведет к инвалидацииpostData()
и, соответственно, отмене/реджекту выкинутого им промиса;либо изменение
this.flag
приведет к инвалидацииpostData()
, в результате чего он будет перезапущен, но выкинутый промис будет переиспользован и я в итоге получу значениеgetJSON(url2)
;либо после завершения работы
getJSON(url1)
компьютед будет вызван еще раз, но т.к.this.flag
изменился, выполнение пойдет по второму пути, запуститсяgetJSON(url2)
и уже после его завершения я получу его результат.А что помешает мне вызвать
postData()
вне реактивного контекста? Вы же выше писали, что такое возможно и в этом случае мне нужно будет самостоятельно поймать промис и дождаться его выполнения?Еще один вопрос: а обрабатываете ли как-то ситуации, когда в процессе выполнения компьютеда вне реактивного контекста какая-то из его зависимостей меняется? Что-то вроде такого:
В данном примере что вернет
postData()
вызванный вне реактивного контекста, если значениеthis.flag
изменится до завершения вызоваgetJSON()
?Upd: заглянул в реализацию и оказалось, что для того чтобы "магия" автообновления работала, библиотека патчит React и этот функционал не будет работать в React 19.
Мне нравится концепт сигналов, но такое решение не кажется хорошей идеей.
Штож, никто и не говорит, что все решения принятые командой React идеальные.
Тут речь о том, что проблем с HOC'ами в MobX нет. Конкретно относительно Ваших тезисов хотя бы потому, что MobX не предлагает создавать HOC'и, он лишь предоставляет один для превращения обычных компонентов в реактивные. Это как
memo()
в React, который делает компонент чистым.Но технически - да, можно и локальным состоянием прекрасно управлять без большей части хуков. Получается очень приятный такой MVVM. На всякий случай уточню, что речь про бизнес логику.
Выше по комментариям были разные примеры кода на MobX. Если их не достаточно - придумайте задачку и возможно мы с Вами сравним решения.
Кстати, по поводу HOC'ов - они не обязательно обязаны создавать дополнительную ноду в React дереве)
Даже в самом названии ООП нет слова "классы", речь об объектах. А наследование - лишь один из принципов. Можем хоть к Википедии обратиться. Ну или кидайте свои более правильные ссылки)
Да не, апеллируете к авторитету =)
Откуда информация? У Вас в предложении отдельные части не согласуются.
А разве на функции перешли не потому, что они лучше ложились конкретно на концепцию файберов (это не точно, ссылку искать лень).
Еще раз напомню, что кроме React есть другие инструменты, например Angular, в котором на классах все прекрасно организовано.
Субъективное суждение.
Не понимаю, как оно относится к моему тезису про то, что Java прекрасно живет с ООП и никто там даже не пытается от него убежать. Выглядит как эмоциональный выброс.
Выше в другом комментарии я писал, что мне кажется что текущая ФЕ повестка как раз давно уже звучит как "ООП - отстой". Лично я каждый раз спрашиваю того, кто так заявляет в чем причина такого мнения. Еще ни разу не получил конкретного аргумента. Ждем Вашу статью.
Вы немного не так поняли мой тезис. Я имел в виду, что понятие "магия" как будто зависит от того, кто его применяет. Я специально привел Вам примеры случаев, когда люди могут не вдаваться в подробности устройства тех или иных техник, и тогда они превращаются для них в магию. Стоит в этом разобраться (даже поверхностно) - и вся "магия" уступает место логичному и предсказуемому поведению.
Новой, но не чуждой. Мой тезис был про то, что хуки - априори не новый подход, а костыль.
Вы тут имеете в виду непосредственно разработчиков библиотеки, или тех кто ей пользуется? Мотивы первых мне не известны, а вторые думаю ясно почему "моментально пересели".
Кстати, Вы же знаете, что функции до сих пор не могут полностью заменить классы?
Это я не понял, про что Вы. На всякий случай поясню - я не за/против магии, я лишь говорю, что "магия" = "я не понимаю как оно там работает" и этот принцип для разных людей работает на разных масштабах. Как Event Loop - кто-то знает как он работает в деталях, а кому-то это не интересно.
Вы не до конца прочитали этот пункт:
В официальной документации есть раздел про переиспользование логики за счет выделения ее в кастомные хуки. И это то, что многие разработчики считают плюсом.
При этом - их можно использовать в нескольких разных компонентах, но компоненты будут прибиты к этим хукам намертво. В то время как компонент можно завернуть в разных HOC'и без вмешательства в его нутро.
О чем тут речь? Что Вы там прокидываете из одного HOC'а в другой?
Насколько мне известно, в Go не отказались от OOP. Там есть структуры и множество способов выстраивать отношения между ними. Наследование - не единственный признак ООП;
В данном случае вы апеллируете к авторитету. С тем же успехом можно сказать, что "в современных языках типа Rust и фреймворках типа Angular от ООП не отказались не просто так";
Тем не менее одна из самых популярных платформ и большинство инструментов и библиотек в ней используют именно ОО подход и паттерны.
Я не говорю, что ООП лучше ФП, но и заявлять обратное тоже считаю некорректным. У каждого подхода свои плюсы/минусы и область применения.
Любая более-менее сложная технология неотличима от магии. Если не вдаваться в принципы работы используемого инструмента, то даже самый простой может работать как магия и подкидывать сюрпризы где их не ждешь. Пара примеров:
Выше в комментариях мы обсуждали сигналы, и там тоже есть "магия" обновления нативных нод, которая при незнании может обернуться тем, что компонент начнет обновляться частично.
Тот же контекст, который Вы упоминаете, при неправильном использовании приводит к большому количеству лишних обновлений UI. Если попробуете погуглить, то можете даже обнаружить что многие люди в принципе считают контекст плохой практикой, "потому что он приводит к обновлению всего и вся" (на самом деле это не так).
Интересно, но когда React был на классах - HOC'и были крутым паттерном переиспользования логики, но как-то со временем все извратилось и сейчас то же самое говорят про хуки, хотя на самом деле:
Хуки - совершенно чуждая любому здравомыслящему разработчику концепция, просто создатели React не придумали ничего лучше для функциональных компонентов. Попробуйте вспомнить использование подобного подхода где-то еще.
Хуки позволяют переиспользовать логику ровно в той же мере, что и обычные утилитарные функции. Вот только любой компонент на хуках намертво к ним прибит, в то время как логика HOC полностью отделена от логики компонента и они оба могут быть использованы независимо.
Используя хуки
useEffect()
,useCallback()
иuseMemo()
вы порождаете новую функцию (и замыкание, которое кстати дороже класса) на каждый рендер, даже если она в итоге не будет использована. Это дополнительно нагружает GC. Лично я не стану заявлять, что лишние статичные ноды в React-дереве бьют по производительности сильнее.Более того, проверка зависимостей этими хуками не бесплатна (как и внутренняя логика их вызова в React) и происходит каждый рендер. Именно поэтому использование
useMemo()
иuseCallback()
получается таким противоречивым - вроде по логике нужно заворачивать в них все, что потом попадает в пропы дочерних компонентов, а вроде неизвестно - мемоизированы ли дочерние компоненты и будет ли это иметь смысл?Использование хуков увеличивает функцию рендера, задача которой преобразовать входные данные и состояние в UI. Вместо этого в той же функции мы описываем инициализацию, эффекты и деструктуризацию. Очень странно слышать, что функция перегруженная ответственностью читается легче, чем несколько отдельных самостоятельных функций.
Если Вы прокидываете данные между HOC'ами, вы скорее всего делаете что-то неправильно (например, разделяете то, что не должно быть разделено). То, что Вам приходится передавать значения между хуками - это как раз следствие того, что на этот костыль наложили слишком много ответственности. Например,
useMemo()
иuseCallback()
можно заменить простымmemoize-one
, который кстати можно вызывать в условиях и циклах.Но это все абстрактно. Если говорить предметно, про тот же MobX - там существует один единственный HOC, у которого отсутствуют описанные Васм проблемы и его использование позволяет отказаться от большей части хуков (особенно от самых неудачных), сделав код сильно более читаемым.
Посмотрел - судя по всему сигнал выдает себя за React элемент, что позволяет его срендерить в JSX. Интересная оптимизация, но к сожалению работает только при использовании в качестве контента. Если использовать этот же сигнал в выражении или в качестве свойства - работать не будет =(
В данном примере содержимое просто не обновляется =( Первое еще можно пофиксить с помощью
useComputed()
:А вот чтобы обновлялся второй элемент, придется использовать
useSignals()
, но в этом случае оптимизация просто перестанет работать (сообщения начнут сыпаться в консоль).На моем опыте - если библиотека предоставляет несколько вариантов поведения, зачастую команда останавливается на каком-то одном, чтобы не нарваться на неожиданности при рефакторинге. Поэтому, могу предположить, что в реальном проекте принято во всех компонентах использующих сигналы всегда добавлять
useSignals()
.Как это без ведома - посмотрите в консоль, там будет вывод "render" на каждый рендер.
Ну так в данном случае вы не используете значение в основном компоненте, но дочерние будут рендериться каждый раз при изменении. Тут ничего удивительного. Грубо говоря, любой менеджер состояния так себя поведет, просто в одних за использование считается непосредственное чтение (MobX, Reatom, Recoil и тд), а в других - использование хука (Redux, Effector и тд).
Здравствуйте. Как написано в статье - большинство критериев оценивалось поверхностно, на основании данных с официальных сайтов библиотек и нашей интуиции. Так, например, авторы эффектора часто заявляют, что их библиотека производительнее, чем Redux и другие конкуренты - вот мы и поверили на слово. Вы сами написали, что вокруг Redux витает много мифов. Эти мифы и стереотипы в нашем случае тоже повлияли на оценку.
По поводу бенчмарков добавлю: достаточно сложно придумать какой-то хороший пример, который бы закрывал все потребности в инструменте и одновременно хорошо подходил для бенчмарканья (чтобы в нем и списки были, и асинхронные действия, и связь между модулями и т.д.). Поэтому во второй раз мы их тоже не делали, а отдельно рассматривали конкретные аспекты работы инструментов, важные для нас, о которых написано в статье.
Присоединюсь к другим комментаторам и с удовольствием послушаю аргументы в пользу этого тезиса. Скиньте ссылку на статью, когда будет готова.
Функциональный стиль не является плюсом - это просто инструмент, такой же как и ООП, иммутабельность, чистые функции и многое другое. Он позволяет за счет определенных ограничений получить конкретные преимущества в конкретной ситуации, но сам по себе не является преимуществом или недостатком.
А еще подскажите, что в Вашем понимании "магия"? И почему она и HOC'и это плохо?
Я не эксперт по сигналам преакта, но кажется что чтобы отделные нативные ноды могли быть реактивными, нужна какая-то логика в самой библиотеке (как, например, у spring есть свои версии нативных элементов) или трансформация при сборке. Решил глубоко не копать, а просто проверить ваше предложение на практике (песочница), и там ожидаемо обновляется весь компонент - можете сами проверить. Я ничего не упускаю?
Cигналы из Preact действительно крутая штука. Я полагаю они не рассматривались по нескольким причинам.
Во-первых, задолго до внедрения эффектора у нас был эксперимент по использованию Preact, но он не взлетел из-за плохой совместимости с некоторыми внутренними решениями, которые уже существовали на тот момент. Полагаю, что этот неудачный эксперимент и то, что сигналы обычно не рассматриваются отдельно от Preact привели к тому, что их не было в табличке сравнения (та же магия обновления нод, о которой Вы упомянули работает только вместе с Preact).
Во-вторых, если сравнивать с MobX, то тут у последнего для нас есть несколько преимуществ:
Объектный стиль получается гораздо лаконичнее - нам не нужно явно писать singnal/computed в подавляющем большинстве случаев, получается обычный JS +минимум бойлерплейта.
MobX все же популярнее и имеет более обширное комьюнити. Мне также не известно крупных проектов, использующих сигналы Preact без самого Preact (тут возможно информация устарела).
Со стороны кажется, что каждый из вас по-своему прав:
В примере есть
autorun()
, использующий значение реактивной переменной. Благодаря тому, что обновления распространяются синхронно на каждой итерации происходит вызов реакции. Т.е. реактивная система действительно задействована.Разные СТМ по-разному кэшируют вычисления, что может сильно повлиять на результаты бенчмарка. Полагаю, речь о том, чтобы сделать дерево зависимостей чуть сложнее (добавив больше компьютедов).
Если Вы еще не писали про все эти проблемы в поддержку, то можете скинуть мне в личку чуть более подробное описание (например, что именно в плеере не работает) и шаги для воспроизведения (особенно интересно про фотоальбомы) и я передам информацию ответственным командам.
Думаю, в статье есть ответ на этот вопрос =/
Добавлю к этому тот факт, что указанные материалы к сожалению отсутствуют в поисковой выдаче по запросу "проблемы effector", что сильно уменьшает вероятность того что они будут учитываться в процессе принятии решения 😔
Получается, если в рамках одного "компьютеда" я вызову одну и ту же псевдо-синхронную функцию дважды с одним и тем же набором аргументов - она вызовется дважды? А если в двух разных "компьютедах"?
А при повторном вызове (после резолва промиса)? Кеширование не привязано к реактивном контексту?
Тоже думал что может быть утилита принимающая на вход массив коллбеков, но надеялся что может быть решение поизящнее.
Т.е. повторный вызов при инвалидации зависимостей и повторный вызов после резолва промиса отличаются. Получается, что при повторном выполнении "компьютеда" даже если все функции внутри будут вызваны в том же порядке и с теми же аргументами - они будут вызваны повторно, верно? В принципе, это имеет смысл.
Ссылки на материалы, которые мы непосредственно использовали в исследованиях или при подготовке статьи указаны по месту (там, где идет соответствующее повествование).
Под "статьей трехлетней давности" Вы имеете в виду ссылку на пост про проблемы Effector? Кмк, возраст поста тут не имеет значения, он скорее призван показать как трудно было во времена нашей истории (да и сейчас) найти любую полезную альтернативную информацию конкретно про Effector.
Такой таблички нет, но некоторые концепции из $mol_wire мы какое-то время обсуждали. Пользуясь случаем задал Вам пару вопросов в комментарии к соответствующему посту тут.
Если API описанное в статье еще актуально, то для создания асинхронных компьютедов все асинхронные функции превращаются в псевдо-синхронные работающие на Suspense API. Пара вопросов по этому поводу:
Чтобы обработать исключения Вы каждый раз в блоке
catch
вставлять проверку на промис (может есть смысл сделать утилиту типаrethrow()
?). Учитывая, что синхронная функция внешне не отличается от псевдо-синхронной - Вы такие проверки делаете всегда во всехcatch
'ах?Можно ли использовать одну и ту же функцию внутри одного компьютеда несколько раз? Для каждой нужно создать псевдо-синхронную версию или кеширование данных для каждого набора аргументов происходит на уровне компьютеда?
Если кеширование происходит на уровне компьютеда, то что если я обращусь к нему из-за пределов реактивного контекста?
Псевдо-синхронные функции ждут выполнения друг друга (см. пример выше). Но что если я хочу отправить несколько запросов параллельно? Нужно создавать функцию-обертку и уже ее сделать псевдо-синхронной?
В рамках выполнения компьютеда отличаются ли как-то "перезапуск из-за обновления зависимостей" от "перезапуска после резолва промиса"? Видел в Ваших примерах использование
sleep(500)
для реализацииdebounce
'а - это тоже псевдо-синхронная функция? Если да, то не значит ли это что задержка сработает только один раз при первом обращении к компьютеду?В данном случае речь о том, что есть такой тренд есть в среде React-разработчиков (к которым мы относимся) и он достаточно популярен . Я ни в коем случае не говорю за абсолютно всех разработчиков.
Angular - очень интересный фреймворк, в разработке собственной архитектуры мы поглядываем в том числе на идеи, которые реализованы в нем и некоторые нам хорошо подходят.
Примерно так и было. В статье я как раз пишу, что мы выбрали Effector отчасти "на волне хайпа". И это было ошибкой.
Как я упомянул в статье, мы делали выводы из слишком маленького объема информации и простого примера. Грубо говоря, "не почуствовали запах в таком масштабе".
Мы внедряли Effector во всем ВКонтакте - это наш основной проект =) Внедрением занимались 2 десятка команд целый год. Сейчас переводим на новую архитектуру, которая для управления состоянием использует MobX.