Да, заинтересовался веб-компонентами только пару месяцев назад, поизучал, уперся в ограничения и дальше дело не пошло. Вероятность, что из них выйдет что-то путное есть, как и вероятность, что они останутся глубоко нишевыми.
Сейчас да, сделать на них точечное обновление, ssr и нормальный проброс не только строковых значений - та еще задача. С массивами работать сложно, shadow dom концепт непростой... Проблем масса. Но как прототип для нативных кастомных элементов - пойдет. Мы же о будущем говорим, и я надеюсь что удобство jsx может придти и в стандарты
Я думаю, что в будущем подходы будут различные в зависимости от задач. Так,
enterprise - продолжит тяготеть к отдельным слоям и фреймворконезависимости. Т.к. я занимаюсь в основном этой сферой, то давно привык собирать приложения "по кубикам", которые не зависят от фреймворков (сборка, роутинг, хранилища, компоненты с адаптерами для разных рендереров, апи, сайд-эффекты, слои реакций и т.п.) - это обеспечивает максимальную гибкость и предотвращает устаревание. На первых этапах (MVP) как правило используется один из мета-фреймворков, но не надолго.
малый бизнес - продолжит пользоваться no-code решениями и готовыми CMS
экспериментальные и личные проекты - сфера конкуренции фреймворков и подходов, т.к. делаются разработчиками скорее для саморазвития, хотя часто и в рамках определенных вспомогательных проектов компании (практически в каждой компании есть такие - от приложения для печати типовых документов с конструктором до просмотрщиков логов, мониторингов и сложной аналитики)
А в целом тренд сейчас идет на расширение нативного HTML API - те же веб-компоненты и воркеры для вычислений (чтобы не блокировать основной тред), пропоузал добавления типов в JS. Они постепенно вытесняют компоненты конкретных фреймворков, а фреймворки добавляют их поддержку. Самая очевидная проблема (SSR), думаю, постепенно решится, и мета-фреймворки следующих поколений будут основаны на них. И, надеюсь, функциональные компоненты наконец уйдут в прошлое, уступив классовым)
Так будет упираться только в скорость копирования в большинстве случаев, а это хорошо оптимизируется. Лишние 100мб дадут лишь несколько секунд сверху. Я не силен в девопсировании, но у нас в рецепте билда на восстановление из кеша и все сопутствующее уходит 1 минута + 30с сборка, так что увеличение размера node_modules влияет незначительно.
На реакте, да, но рефрешем не пользуюсь, поэтому тут не подскажу - в основном в проектах SSR (кстати тоже прекрасно работает в SWC), поэтому использую полную перезагрузку страницы. Если нужно поработать над компонентом - то какой-нибудь стайлгайд.
Когда я перевел сборку проекта (не тестов) с Babel на SWC получилось вот что. Верхние результаты с минификацией, которая тоже переехала с Terser на Terser.SWC. Также здесь включено море плагинов, которые остались без изменений, поэтому результат довольно чистый - изменился только транспайлер
(CI) Babel без прогретого кеша + Terser
#16 22.96 [WEBPACK] finished building node within 20.21 seconds
#16 40.69 [WEBPACK] finished building web within 36.324 seconds
(CI) SWC + Terser.SWC
#16 13.14 [WEBPACK] finished building node within 10.57 seconds
#16 29.60 [WEBPACK] finished building web within 24.831 seconds
(Локальная сборка без минификации) Babel без прогретого кеша
[WEBPACK] finished building node within 5.443 seconds
[WEBPACK] finished building web within 6.745 seconds
(Локальная сборка без минификации) Babel с прогретым кешем
[WEBPACK] finished building node within 2.348 seconds
[WEBPACK] finished building web within 4.044 seconds
(Локальная сборка без минификации) SWC
[WEBPACK] finished building node within 1.609 seconds
[WEBPACK] finished building web within 3.742 seconds
Все на одной машине, запуски делал раз по 5, среднее значение скорости менялось не сильно. Выводы получились следующие:
SWC не требует прогревания кеша, что максимально отражается в CI
Terser.SWC - очень быстрый минификатор, без него результаты не такие поразительные
Переезд на SWC получился почти безболезненный (да, его нужно настроить и подобрать альтернативные плагины, как у babel, но мои кейсы с недавним добавлением swc-loadable-components закрылись полностью). Из негативных моментов - нет плагина для оптимизации lodash и сахарные декораторы SWC несовместимы с некоторыми библиотеками, поэтому пришлось переписать их на функциональные.
Размер node_modules действительно вырос, но цифры у меня не сохранились.
Также делал замеры на более крупных проектах, там тоже локальная сборка примерно на 20% ускорилась относительно бабеля с кешем. На мой взгляд, отличный результат "на ровном месте" почти без изменения кода
Про "никто не меняет вью" в принципе согласен, но я скорее для спортивного интереса поддерживаю архитектурные части изолированными и абстрагированными. Не только Реакт могу поменять на Preact/Inferno/SolidJS и экспериментирую с заменой через адаптеры на другие рендереры (даже веб-компоненты), но и MobX на другие реактивные библиотеки, тот же Solid mutableStore. Это позволяет мне лично расти как специалисту и создавать долгоподдерживаемые проекты, заодно сравнивая перфоманс и набор фичей и оттачивая дизайн системы. И в целом стараюсь не завязываться на конкретные особенности (ex. версия определенной либы, которая рендерит асинхронно). И другие архитектурные части тоже делаю самодостаточными и независимыми. Поэтому хаки чисто для MobX в виде асинхронного автобатчинга не рассматриваю.
Конечно, можно и с ним жить, и утилиты подбить так, что будет и SSR, и нормальный стек вызовов, и соблюдена очередность реакций. Можно и на альтернативные библиотеки наворачивать такой функционал, но все равно мне ближе явный подход - где изменяется стор, оборачивать в batch (runInAction в mobx). Так добавляется бойлерплейт, но упрощается понимание за счет синхронности операций в и так непростом асинхронном мире JS, а в ряде случаев - улучшается перфоманс (незначительно, но в перегруженных компонентами страницах асинхронщина может привести к глитчам).
Забыл еще один пункт - роутинг у меня сейчас построен именно на синхронной реакции, асинхронная собьет определенную логику при уходе со страницы, но это недочет, надо будет добавить поддержку.
И еще - в асинхронной реакции может уже не быть той сущности, с которой она должна работать (компонент размаунтился, this = undefined, состояние модульного стора очистилось).
По существу особо сказать что-то против вашего подхода нечего, но к себе в проекты не потащу, т.к. вижу в этом не упрощение, а усложнение. Код без batch конечно становится лаконичнее, но добавляется асинхронный сайд-эффект с внутренней логикой, которые я не уважаю (в частности потому и хуки не использую).
Самое очевидное - SSR, реакции, которые навешиваются до этапа рендеринга должны отработать, чтобы рендер произошел с финальным состоянием. С асинхронщиной отрендерится с негидрируемым состоянием (хотя в примере выше первый колбэк вызывается синхронно, повторные - не сработают и повиснут в эвент лупе ноды, не влияя на рендер)
Цепочки авторанов (в первом меняется параметр, который слушает второй авторан, в третьем прослушивается результат второго) - хоть Реакт 18 и добавил асинхронности в рендер, я на это не полагаюсь, и на каждый рендер подготавливаю финальное состояние, чтобы отрендерилось 1 раз. Т.к. ориентируюсь на более-менее свободную замену частей приложения (в том числе рендерера типа Реакта), то важно синхронно подготовить данные для избежания ререндеров - это просто хороший тон и предотвращение проблем.
Ну и минорное замечание к решению выше - если подменять одну функцию другой и передавать в setTimeout(fn) то контекст теряется и в стеке вызовов будет batch вместо реального имени функции. Фиксится парой строк, но все равно об этом думать надо
Оказывается, это Mazaa шифруется под новым ником, интересно)
В реальных задачах я могу вешать 5 авторанов последовательно, и даже если меняется их порядок - то где-то работает некорректно. Конечно, это антипаттерн и нужны прямые руки и продумывать лучше, но как это всегда бывает - нужно все срочно и потоки МР по 5-10к строк ежедневные, вангую, что с асинхронным вызовом было бы проблем больше, чем порядок поменять.
Да, таких решений немало, но все они завязаны на setTimeout, я же выше писал про синхронные изменения - это тоже очень важно. Вот еще реализация, без подмены на кастомную функцию. Если вам не нужны синхронные подписки - хорошо, но у меня кодовая база именно на них завязана
configure({
reactionScheduler: f => setTimeout(f, 1)
});
Тоже пользуюсь Find Usages, дебаг очень простой. А чтобы не было ворнингов все эшены автоматом оборачиваются в action, а для батчинга runInAction, если после асинхронных вызовов. Я пока не считаю, что оправданно вырубать enforceActions - многое в коде зависит от батчинга (мне нужно чтобы несколько переменных менялись сразу, а не постепенно - не для рендера в Реакте, а для autorun, которые например отслеживают форму - надо чтобы и value поменялись, и isFocused, и валидаторы, и авторан вызвался 1 раз с финальными данными). И таких ситуаций много. Плюс я стараюсь делать библиотеко- и фреймворко-независимую архитектуру, а в альтернативах MobX тоже используются колбэки системной функции для батчинга, иначе его просто не выстроить синхронно.
К теме дебага - все экшены у меня еще логируются в визуальный интерфейс (история вызова + время на исполнение) в виде stack bars chart, что максимально сужает поиск по Find Usages.
Немного вклинюсь по поводу юзстора - решается оберткой (я пишу на классовых компонентах, поэтому пример для них)
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 превращаются в
Так получается реактивность без сахарных декораторов (чтобы использовать не Babel, а более шустрый SWC транспайлер), с отдельной логикой для SSR и без бойлерплейта.
К оригинальному комменту - тоже один контекст на все, это супер-эффективно, в отличие от DI разрозненных сторов для "изоляции". Когда нужно - расширяешь глобальный, когда не нужно - очищаешь.
Не могли бы привести пример предупреждений в консоли? У меня нету ни в одном проекте, как вызвать?
"Неявные подписки" - это отличная практика вынесения сложности за рамки основного кода. То есть работаешь по виду с обычными объектами, а на изменения определенных параметров, в том числе глубоко вложенных, можно еще и подписываться, это же мечта из 2000-х. Я тогда море ухищрений делал, чтобы добиться реактивного рендеринга, теперь же все очень просто.
Про стор, в котором все намешано - это конечно антипаттерн, но никто не заставляет его так организовывать. Особенно это касается экшенов (функций изменения стора), намного эффективнее их выносить, чтобы они имели доступ к разным сторам. Поэтому нельзя отнести это отнести в минусы инструмента, который делает объекты реактивными и дает полную свободу в реализации.
"В эффекторе же у вас есть только события, которые способны изменять стор" - ну и в Mobx есть функции-экшены, которые изменяют стор, только без необходимости иммутабельно писать как в редаксе (store, data) = > ({...store, items: store.items.map(item => ({...item, isViewed: data.id === item.id}))}) , разводя дубляж и значительно усложняя код, и заводя прослойку в виде событий, которые являются чистым бесполезным бойлерплейтом. Все, что нужно - вызвать функцию, она меняет нужный параметр в сторе, автоматически происходит эффективный перерендеринг.
Насчет "полегче" - аргумент, конечно, хороший, если измерения проводить в маленьких проектах, где размер библиотек намного превышает размер бизнес-логики, но уже в средних размер приложений с mobx за счет практически отсутствия бойлерплейта уже начинает выравниваться, в крупных - будет меньше.
У меня обычно в проектах разные интерфейсы для renderWait и renderEmpty, поэтому просто пишу get renderWait() { return <SomeMarkup /> } и в самом render() { if (isLoading) return this.renderWait; } . В целом тут только для очень специфических кейсов пригодится унификация и наследование, и если унифицировано - то лучше вынести на уровень выше, чтобы верхний компонент показывал соответствующую разметку, не раздувая нижние компоненты.
А разные типы одного компонента с общим функционалом сделал бы через class BranchWrapper{ render() { return Children.clone(this.props.children, { getChildNode: () => logic; }) } } . Так что варианты есть. Хотя это как раз React way, по которому не очень хочется идти, ООП довольно сильно усложняет восприятие, на мой взгляд. А еще лучше - через контекст, чтобы совсем отвязаться от пути Реакта - class SomeBranch { handleButtonClick = () => this.context.getNodes(this.props.branchId) }. Думаю, так проще, чем наследовать, но у каждого свой путь)
А где во фронте, если не секрет, вам пригодилось наследование? Мне лично - нигде, кроме как отнаследовать класс от реакта, чтобы появились методы жизненного цикла и в играх. В остальном никогда. Если нужна какая-то функция в нескольких компонентах то просто импортится из утилит и вызывается, если какая-то логика выборки переиспользуется - то делается геттер в хранилище.
Хуки вообще странный концепт, я с ними тоже не подружился. Ничего мне не нужно от того что они продают - а именно объединенный в один вызов useEffect маунт-размаунт и "переиспользуемость". Семантичные методы класса для жизненного цикла удобней и ssr поддерживают, а переиспользуемость легко и в классах реализуется. В остальном только проблемы от кода, превращающего в кашу из длинных грязных функций без явных слоев логики.
Все, что в статье приведено - это проблемы React way, то есть когда простой фреймворк обрастает концептами и фичами, которые намного эффективнее решались бы сторонними решениями. Но никто на самом деле не навязывает их, можно использовать только нужное, для меня это:
Оформление компонентов в классы
3 метода жизненного цикла - componentWillMount (для SSR+client), componentDidMount (только client) и componentWillUnmount. Я бы предпочел, чтобы они более семантично и короче назывались, но это не большая проблема
Доступ к context (некоему глобальному объекту) из любого компонента без дополнительных импортов
Грамотно сделанный HTML-in-JS шаблонизатор со скоупом функции и JS-in-HTML
(иногда) Рефы, чтобы не генерировать уникальные идентификаторы для определенных частей компонента
Для всего остального есть удобные реактивные решения, о которых все знают. И для подписки на изменения, и для ререндеринга только при изменении используемых параметров, и для кеширования в геттерах сложной логики, и для батчинга вносимых изменений. Если бы React скорцентрировался чисто на функционале выше и максимизировал перфоманс и размер исходя чисто из этих фич - я был бы счастлив. Но сейчас он дает простор и тем, кто хочет повозиться с хуками, с нереактивными пробросами пропсов, с иммутабельными изменениями через редюсеры, с невидимым вооруженному глазу состоянием внутри функции, с комбинированными в один вызов жизненными циклами, с асинхронными компонентами, разными системами стилизации - что на самом деле не такой уж большой минус, так как развивает экосистему, которая действительно стала огромной. Но заставлять все это пробовать и использовать - этого нет, можно обойтись минимумом, нужным конкретному разработчику, и дополнить решениями, которые он считает адекватными.
SWC кстати стал отличным компилятором, перевел уже несколько проектов на него в качестве лоадера для Webpack. Умеет многое из того, что умеет babel, включая полифиллы по usage исходя из browserslist, при этом работает быстрее на 20-25% в моих кейсах. Но плохо умеет в "сахарные" декораторы, приходится переписывать на функциональные, и нет плагина для оптимизации размера lodash (в случае если делается `import lodash fom 'lodash'; lodash.get()`). Минификация еще намного быстрее, чем в Terser. В целом - вполне production-ready.
Недавно было найдено решение https://github.com/DmitryKoterov/conditional-aggregate-webpack-plugin , но есть определенный issue, который я там описал. С доработкой все работает достаточно стабильно, и пересборка блочится пока не будут выполнены необходимые условия, измененные файлы агрегируются.
Начал вебмастерить сайты в 2004, после того как надоело переустанавливать винду и крякать игры (для школьника норм заработок был), первый крупный продовый проект был на Mambo/Joomla тогда же (школьный сайт с гостевой книгой, блогом, кастомным шаблоном, небезызвестными снежинками и форумом на phpbb). Чуть позже запилил стриминговый радио-сайт (плеер через flash) с трансляцией через Winamp, с друзьями до сих пор иногда запускаем.
После школы уже пошли заказы на сайты на Wordpress, Typo3, OpenCart, ModX, много сайтов-визиток. Параллельно с универом и после него настругал десятки сайтов на CouchCMS (однозначно лучший движок на мой взгляд). Интересные времена были. Как в статье и описано приходилось и в базу, и в хостинг, и в администрирование, и контент, и дизайн, и верстку, и скрипты на php, а тогдашнее сео заставляет вздрагивать (те же публикации на тысячах досок объявлений, чтобы войти в топ по перекрестным ссылкам, или невидимый текст белым на белом). Хотя веб-мастеров было немало, толково сайт, который не разъезжается, кроссбраузерный, и с минимальной резиновостью сделать могли очень немногие.
Иногда жаль, что по навыкам практически ничего с того времени не пригодилось.
Да, заинтересовался веб-компонентами только пару месяцев назад, поизучал, уперся в ограничения и дальше дело не пошло. Вероятность, что из них выйдет что-то путное есть, как и вероятность, что они останутся глубоко нишевыми.
Сейчас да, сделать на них точечное обновление, ssr и нормальный проброс не только строковых значений - та еще задача. С массивами работать сложно, shadow dom концепт непростой... Проблем масса. Но как прототип для нативных кастомных элементов - пойдет. Мы же о будущем говорим, и я надеюсь что удобство jsx может придти и в стандарты
Спасибо за статью, интересно!
Я думаю, что в будущем подходы будут различные в зависимости от задач. Так,
enterprise - продолжит тяготеть к отдельным слоям и фреймворконезависимости. Т.к. я занимаюсь в основном этой сферой, то давно привык собирать приложения "по кубикам", которые не зависят от фреймворков (сборка, роутинг, хранилища, компоненты с адаптерами для разных рендереров, апи, сайд-эффекты, слои реакций и т.п.) - это обеспечивает максимальную гибкость и предотвращает устаревание. На первых этапах (MVP) как правило используется один из мета-фреймворков, но не надолго.
малый бизнес - продолжит пользоваться no-code решениями и готовыми CMS
экспериментальные и личные проекты - сфера конкуренции фреймворков и подходов, т.к. делаются разработчиками скорее для саморазвития, хотя часто и в рамках определенных вспомогательных проектов компании (практически в каждой компании есть такие - от приложения для печати типовых документов с конструктором до просмотрщиков логов, мониторингов и сложной аналитики)
А в целом тренд сейчас идет на расширение нативного HTML API - те же веб-компоненты и воркеры для вычислений (чтобы не блокировать основной тред), пропоузал добавления типов в JS. Они постепенно вытесняют компоненты конкретных фреймворков, а фреймворки добавляют их поддержку. Самая очевидная проблема (SSR), думаю, постепенно решится, и мета-фреймворки следующих поколений будут основаны на них. И, надеюсь, функциональные компоненты наконец уйдут в прошлое, уступив классовым)
Кстати по поводу установки зависимостей в CI - они как правило меняются не часто, и можно сделать кеш слоев именно в CI
Так будет упираться только в скорость копирования в большинстве случаев, а это хорошо оптимизируется. Лишние 100мб дадут лишь несколько секунд сверху. Я не силен в девопсировании, но у нас в рецепте билда на восстановление из кеша и все сопутствующее уходит 1 минута + 30с сборка, так что увеличение размера node_modules влияет незначительно.
На реакте, да, но рефрешем не пользуюсь, поэтому тут не подскажу - в основном в проектах SSR (кстати тоже прекрасно работает в SWC), поэтому использую полную перезагрузку страницы. Если нужно поработать над компонентом - то какой-нибудь стайлгайд.
Когда я перевел сборку проекта (не тестов) с Babel на SWC получилось вот что. Верхние результаты с минификацией, которая тоже переехала с Terser на Terser.SWC. Также здесь включено море плагинов, которые остались без изменений, поэтому результат довольно чистый - изменился только транспайлер
(CI) Babel без прогретого кеша + Terser
(CI) SWC + Terser.SWC
(Локальная сборка без минификации) Babel без прогретого кеша
(Локальная сборка без минификации) Babel с прогретым кешем
(Локальная сборка без минификации) SWC
Все на одной машине, запуски делал раз по 5, среднее значение скорости менялось не сильно. Выводы получились следующие:
SWC не требует прогревания кеша, что максимально отражается в CI
Terser.SWC - очень быстрый минификатор, без него результаты не такие поразительные
Переезд на SWC получился почти безболезненный (да, его нужно настроить и подобрать альтернативные плагины, как у babel, но мои кейсы с недавним добавлением swc-loadable-components закрылись полностью). Из негативных моментов - нет плагина для оптимизации lodash и сахарные декораторы SWC несовместимы с некоторыми библиотеками, поэтому пришлось переписать их на функциональные.
Размер node_modules действительно вырос, но цифры у меня не сохранились.
Также делал замеры на более крупных проектах, там тоже локальная сборка примерно на 20% ускорилась относительно бабеля с кешем. На мой взгляд, отличный результат "на ровном месте" почти без изменения кода
Про "никто не меняет вью" в принципе согласен, но я скорее для спортивного интереса поддерживаю архитектурные части изолированными и абстрагированными. Не только Реакт могу поменять на Preact/Inferno/SolidJS и экспериментирую с заменой через адаптеры на другие рендереры (даже веб-компоненты), но и MobX на другие реактивные библиотеки, тот же Solid mutableStore. Это позволяет мне лично расти как специалисту и создавать долгоподдерживаемые проекты, заодно сравнивая перфоманс и набор фичей и оттачивая дизайн системы. И в целом стараюсь не завязываться на конкретные особенности (ex. версия определенной либы, которая рендерит асинхронно). И другие архитектурные части тоже делаю самодостаточными и независимыми. Поэтому хаки чисто для MobX в виде асинхронного автобатчинга не рассматриваю.
Конечно, можно и с ним жить, и утилиты подбить так, что будет и SSR, и нормальный стек вызовов, и соблюдена очередность реакций. Можно и на альтернативные библиотеки наворачивать такой функционал, но все равно мне ближе явный подход - где изменяется стор, оборачивать в batch (runInAction в mobx). Так добавляется бойлерплейт, но упрощается понимание за счет синхронности операций в и так непростом асинхронном мире JS, а в ряде случаев - улучшается перфоманс (незначительно, но в перегруженных компонентами страницах асинхронщина может привести к глитчам).
Забыл еще один пункт - роутинг у меня сейчас построен именно на синхронной реакции, асинхронная собьет определенную логику при уходе со страницы, но это недочет, надо будет добавить поддержку.
И еще - в асинхронной реакции может уже не быть той сущности, с которой она должна работать (компонент размаунтился, this = undefined, состояние модульного стора очистилось).
По существу особо сказать что-то против вашего подхода нечего, но к себе в проекты не потащу, т.к. вижу в этом не упрощение, а усложнение. Код без batch конечно становится лаконичнее, но добавляется асинхронный сайд-эффект с внутренней логикой, которые я не уважаю (в частности потому и хуки не использую).
Самое очевидное - SSR, реакции, которые навешиваются до этапа рендеринга должны отработать, чтобы рендер произошел с финальным состоянием. С асинхронщиной отрендерится с негидрируемым состоянием (хотя в примере выше первый колбэк вызывается синхронно, повторные - не сработают и повиснут в эвент лупе ноды, не влияя на рендер)
Цепочки авторанов (в первом меняется параметр, который слушает второй авторан, в третьем прослушивается результат второго) - хоть Реакт 18 и добавил асинхронности в рендер, я на это не полагаюсь, и на каждый рендер подготавливаю финальное состояние, чтобы отрендерилось 1 раз. Т.к. ориентируюсь на более-менее свободную замену частей приложения (в том числе рендерера типа Реакта), то важно синхронно подготовить данные для избежания ререндеров - это просто хороший тон и предотвращение проблем.
Ну и минорное замечание к решению выше - если подменять одну функцию другой и передавать в setTimeout(fn) то контекст теряется и в стеке вызовов будет batch вместо реального имени функции. Фиксится парой строк, но все равно об этом думать надо
Оказывается, это Mazaa шифруется под новым ником, интересно)
В реальных задачах я могу вешать 5 авторанов последовательно, и даже если меняется их порядок - то где-то работает некорректно. Конечно, это антипаттерн и нужны прямые руки и продумывать лучше, но как это всегда бывает - нужно все срочно и потоки МР по 5-10к строк ежедневные, вангую, что с асинхронным вызовом было бы проблем больше, чем порядок поменять.
Да, таких решений немало, но все они завязаны на setTimeout, я же выше писал про синхронные изменения - это тоже очень важно. Вот еще реализация, без подмены на кастомную функцию. Если вам не нужны синхронные подписки - хорошо, но у меня кодовая база именно на них завязана
Тоже пользуюсь Find Usages, дебаг очень простой. А чтобы не было ворнингов все эшены автоматом оборачиваются в action, а для батчинга runInAction, если после асинхронных вызовов. Я пока не считаю, что оправданно вырубать enforceActions - многое в коде зависит от батчинга (мне нужно чтобы несколько переменных менялись сразу, а не постепенно - не для рендера в Реакте, а для autorun, которые например отслеживают форму - надо чтобы и value поменялись, и isFocused, и валидаторы, и авторан вызвался 1 раз с финальными данными). И таких ситуаций много. Плюс я стараюсь делать библиотеко- и фреймворко-независимую архитектуру, а в альтернативах MobX тоже используются колбэки системной функции для батчинга, иначе его просто не выстроить синхронно.
К теме дебага - все экшены у меня еще логируются в визуальный интерфейс (история вызова + время на исполнение) в виде stack bars chart, что максимально сужает поиск по Find Usages.
Немного вклинюсь по поводу юзстора - решается оберткой (я пишу на классовых компонентах, поэтому пример для них)
В Webpack делаю транформер, который все компоненты, которые экстендят ConnectedComponent превращаются в
Так получается реактивность без сахарных декораторов (чтобы использовать не Babel, а более шустрый SWC транспайлер), с отдельной логикой для SSR и без бойлерплейта.
К оригинальному комменту - тоже один контекст на все, это супер-эффективно, в отличие от DI разрозненных сторов для "изоляции". Когда нужно - расширяешь глобальный, когда не нужно - очищаешь.
Не могли бы привести пример предупреждений в консоли? У меня нету ни в одном проекте, как вызвать?
"Неявные подписки" - это отличная практика вынесения сложности за рамки основного кода. То есть работаешь по виду с обычными объектами, а на изменения определенных параметров, в том числе глубоко вложенных, можно еще и подписываться, это же мечта из 2000-х. Я тогда море ухищрений делал, чтобы добиться реактивного рендеринга, теперь же все очень просто.
Про стор, в котором все намешано - это конечно антипаттерн, но никто не заставляет его так организовывать. Особенно это касается экшенов (функций изменения стора), намного эффективнее их выносить, чтобы они имели доступ к разным сторам. Поэтому нельзя отнести это отнести в минусы инструмента, который делает объекты реактивными и дает полную свободу в реализации.
"В эффекторе же у вас есть только события, которые способны изменять стор" - ну и в Mobx есть функции-экшены, которые изменяют стор, только без необходимости иммутабельно писать как в редаксе (store, data) = > ({...store, items: store.items.map(item => ({...item, isViewed: data.id === item.id}))}) , разводя дубляж и значительно усложняя код, и заводя прослойку в виде событий, которые являются чистым бесполезным бойлерплейтом. Все, что нужно - вызвать функцию, она меняет нужный параметр в сторе, автоматически происходит эффективный перерендеринг.
Насчет "полегче" - аргумент, конечно, хороший, если измерения проводить в маленьких проектах, где размер библиотек намного превышает размер бизнес-логики, но уже в средних размер приложений с mobx за счет практически отсутствия бойлерплейта уже начинает выравниваться, в крупных - будет меньше.
У меня обычно в проектах разные интерфейсы для renderWait и renderEmpty, поэтому просто пишу get renderWait() { return <SomeMarkup /> } и в самом render() { if (isLoading) return this.renderWait; } . В целом тут только для очень специфических кейсов пригодится унификация и наследование, и если унифицировано - то лучше вынести на уровень выше, чтобы верхний компонент показывал соответствующую разметку, не раздувая нижние компоненты.
А разные типы одного компонента с общим функционалом сделал бы через class BranchWrapper{ render() { return Children.clone(this.props.children, { getChildNode: () => logic; }) } } . Так что варианты есть. Хотя это как раз React way, по которому не очень хочется идти, ООП довольно сильно усложняет восприятие, на мой взгляд. А еще лучше - через контекст, чтобы совсем отвязаться от пути Реакта - class SomeBranch { handleButtonClick = () => this.context.getNodes(this.props.branchId) }. Думаю, так проще, чем наследовать, но у каждого свой путь)
А где во фронте, если не секрет, вам пригодилось наследование? Мне лично - нигде, кроме как отнаследовать класс от реакта, чтобы появились методы жизненного цикла и в играх. В остальном никогда. Если нужна какая-то функция в нескольких компонентах то просто импортится из утилит и вызывается, если какая-то логика выборки переиспользуется - то делается геттер в хранилище.
Хуки вообще странный концепт, я с ними тоже не подружился. Ничего мне не нужно от того что они продают - а именно объединенный в один вызов useEffect маунт-размаунт и "переиспользуемость". Семантичные методы класса для жизненного цикла удобней и ssr поддерживают, а переиспользуемость легко и в классах реализуется. В остальном только проблемы от кода, превращающего в кашу из длинных грязных функций без явных слоев логики.
Все, что в статье приведено - это проблемы React way, то есть когда простой фреймворк обрастает концептами и фичами, которые намного эффективнее решались бы сторонними решениями. Но никто на самом деле не навязывает их, можно использовать только нужное, для меня это:
Оформление компонентов в классы
3 метода жизненного цикла - componentWillMount (для SSR+client), componentDidMount (только client) и componentWillUnmount. Я бы предпочел, чтобы они более семантично и короче назывались, но это не большая проблема
Доступ к context (некоему глобальному объекту) из любого компонента без дополнительных импортов
Грамотно сделанный HTML-in-JS шаблонизатор со скоупом функции и JS-in-HTML
(иногда) Рефы, чтобы не генерировать уникальные идентификаторы для определенных частей компонента
Для всего остального есть удобные реактивные решения, о которых все знают. И для подписки на изменения, и для ререндеринга только при изменении используемых параметров, и для кеширования в геттерах сложной логики, и для батчинга вносимых изменений. Если бы React скорцентрировался чисто на функционале выше и максимизировал перфоманс и размер исходя чисто из этих фич - я был бы счастлив. Но сейчас он дает простор и тем, кто хочет повозиться с хуками, с нереактивными пробросами пропсов, с иммутабельными изменениями через редюсеры, с невидимым вооруженному глазу состоянием внутри функции, с комбинированными в один вызов жизненными циклами, с асинхронными компонентами, разными системами стилизации - что на самом деле не такой уж большой минус, так как развивает экосистему, которая действительно стала огромной. Но заставлять все это пробовать и использовать - этого нет, можно обойтись минимумом, нужным конкретному разработчику, и дополнить решениями, которые он считает адекватными.
SWC кстати стал отличным компилятором, перевел уже несколько проектов на него в качестве лоадера для Webpack. Умеет многое из того, что умеет babel, включая полифиллы по usage исходя из browserslist, при этом работает быстрее на 20-25% в моих кейсах. Но плохо умеет в "сахарные" декораторы, приходится переписывать на функциональные, и нет плагина для оптимизации размера lodash (в случае если делается `import lodash fom 'lodash'; lodash.get()`). Минификация еще намного быстрее, чем в Terser. В целом - вполне production-ready.
Недавно было найдено решение https://github.com/DmitryKoterov/conditional-aggregate-webpack-plugin , но есть определенный issue, который я там описал. С доработкой все работает достаточно стабильно, и пересборка блочится пока не будут выполнены необходимые условия, измененные файлы агрегируются.
Asus mb16ac с матовой пленкой, годы таскаю по поездкам к мини пк. Стойки удобные можно из Китая заказать.
Начал вебмастерить сайты в 2004, после того как надоело переустанавливать винду и крякать игры (для школьника норм заработок был), первый крупный продовый проект был на Mambo/Joomla тогда же (школьный сайт с гостевой книгой, блогом, кастомным шаблоном, небезызвестными снежинками и форумом на phpbb). Чуть позже запилил стриминговый радио-сайт (плеер через flash) с трансляцией через Winamp, с друзьями до сих пор иногда запускаем.
После школы уже пошли заказы на сайты на Wordpress, Typo3, OpenCart, ModX, много сайтов-визиток. Параллельно с универом и после него настругал десятки сайтов на CouchCMS (однозначно лучший движок на мой взгляд). Интересные времена были. Как в статье и описано приходилось и в базу, и в хостинг, и в администрирование, и контент, и дизайн, и верстку, и скрипты на php, а тогдашнее сео заставляет вздрагивать (те же публикации на тысячах досок объявлений, чтобы войти в топ по перекрестным ссылкам, или невидимый текст белым на белом). Хотя веб-мастеров было немало, толково сайт, который не разъезжается, кроссбраузерный, и с минимальной резиновостью сделать могли очень немногие.
Иногда жаль, что по навыкам практически ничего с того времени не пригодилось.