Если в приложении есть фон серверных редиректов - его оверхэд посчитать и заметить сложно - пример графиков с пустым 75 перцентилем и с 95 перцентилем, где уже видим что редиректы не бесплатные.
К примеру на tinkoff.ru все приложения будут делать редирект на адрес с "/" в конце.
Но из-за того что редиректов не больше 10% от общего числа запросов, нулевые метрики забивают все остальное.
Возможно стоит сделать отдельный график с отфильтрованными нулевыми значениями?
Метрика - время между redirectStart и redirectEnd.
К примеру на tinkoff.ru все приложения будут делать редирект на адрес с "/" в конце.
Но из-за того что редиректов не больше 10% от общего числа запросов, нулевые метрики забивают все остальное.
Возможно стоит сделать отдельный график с отфильтрованными нулевыми значениями?
Метрика - время между redirectStart и redirectEnd.
👍2
Приятно удивляет время резолва DNS - метрика между domainLookupStart и domainLookupEnd.
До 95 перцентиля (на первой картинке) даже не видно что это время вообще есть - как мне кажется, браузер либо хорошо кэширует результат резолва, либо делает его предварительно - например во время ввода урла в адресной строке, и в метрику уже это время не попадает.
На 99 перцентиле (второе изображение) уже видно, что кто-то из юзеров сталкивается с резолвом DNS, но скорее всего пики связаны с плохим соединением у конкретного клиента.
До 95 перцентиля (на первой картинке) даже не видно что это время вообще есть - как мне кажется, браузер либо хорошо кэширует результат резолва, либо делает его предварительно - например во время ввода урла в адресной строке, и в метрику уже это время не попадает.
На 99 перцентиле (второе изображение) уже видно, что кто-то из юзеров сталкивается с резолвом DNS, но скорее всего пики связаны с плохим соединением у конкретного клиента.
👍1
А вот время на установку TCP и TLS соединения тратят уже большинство пользователей, чем хуже интернет и больше Round Trip Time, тем хуже будет метрика у клиентов.
Тут минус, что perfume.js из коробки не собирает это время и вычисляю косвенно TCP + TLS - но на самом деле Navigation Timing API позволяет собрать их по отдельности.
Это может быть полезно для идентификации проблем с TLS, например какая-то медленная цепочка сертификатов (не сталкивался с таким на практике).
Тут минус, что perfume.js из коробки не собирает это время и вычисляю косвенно TCP + TLS - но на самом деле Navigation Timing API позволяет собрать их по отдельности.
Это может быть полезно для идентификации проблем с TLS, например какая-то медленная цепочка сертификатов (не сталкивался с таким на практике).
👍1
И последняя, но к сожалению самая значимая часть - это время скачивания HTML документа, метрика между responseStart и responseEnd.
Не зря Qwik работает над уменьшением HTML разметки при стриминге - https://www.builder.io/blog/qwik-2-coming-soon
Типичный SSR с React, в нашем случае на Tramvai но мы тут не исключение, создает очень большой payload - это и большое количество информации в head (мета-теги, скрипты, preload теги), и огромное количество тегов в body, и HTTP заголовки вроде cookie и CSP.
Также, значительная часть HTML - это сериализованный стейт, переданный с сервера на клиент.
Концепт Qwik с resumability как раз хорош тем, что в HTML нет дублирующей информации, только то что нужно клиентскому коду для продолжения работы в браузере (для реакта же мы должны иметь серверный стейт для корректной гидрации всего дерева).
Даже с сжатием HTML на стороне балансера, эта часть на мобилках может занимать столько же времени, сколько отвечает сервер.
Не зря Qwik работает над уменьшением HTML разметки при стриминге - https://www.builder.io/blog/qwik-2-coming-soon
Типичный SSR с React, в нашем случае на Tramvai но мы тут не исключение, создает очень большой payload - это и большое количество информации в head (мета-теги, скрипты, preload теги), и огромное количество тегов в body, и HTTP заголовки вроде cookie и CSP.
Также, значительная часть HTML - это сериализованный стейт, переданный с сервера на клиент.
Концепт Qwik с resumability как раз хорош тем, что в HTML нет дублирующей информации, только то что нужно клиентскому коду для продолжения работы в браузере (для реакта же мы должны иметь серверный стейт для корректной гидрации всего дерева).
Даже с сжатием HTML на стороне балансера, эта часть на мобилках может занимать столько же времени, сколько отвечает сервер.
👍4
Подведу некий summary:
- Round Trip Time важен на каждом из многочисленных этапов загрузки HTML, в идеальном мире точка входа в приложение - это CDN
- Редиректы не бесплатные как для клиента, так и для приложения, лучше их не делать или делать поближе к пользователю, например на уровне балансера
- Service Worker может как ускорить, так и замедлить ваше приложение - измеряйте все что можете, пробуйте Navigation Preload, не используйте SW для "галочки" - браузер и так отлично все кэширует
- Раздутый HTML и плохой интернет - это значительная проблема для SSR, которую трудно исправить малыми усилиями
Круто, что есть много браузерных API для мониторинга таких деталей.
- Round Trip Time важен на каждом из многочисленных этапов загрузки HTML, в идеальном мире точка входа в приложение - это CDN
- Редиректы не бесплатные как для клиента, так и для приложения, лучше их не делать или делать поближе к пользователю, например на уровне балансера
- Service Worker может как ускорить, так и замедлить ваше приложение - измеряйте все что можете, пробуйте Navigation Preload, не используйте SW для "галочки" - браузер и так отлично все кэширует
- Раздутый HTML и плохой интернет - это значительная проблема для SSR, которую трудно исправить малыми усилиями
Круто, что есть много браузерных API для мониторинга таких деталей.
🔥9👍5
Забыл про API, которое разочаровало (по крайней мере не очень работает для нас) - Network Information API
По собранным метрикам, у подавляющего большинства пользователей отличный интернет, большая пропускная способность, некий средний RTT - в общем пользы не больше чем от метрики First Input Delay.
По собранным метрикам, у подавляющего большинства пользователей отличный интернет, большая пропускная способность, некий средний RTT - в общем пользы не больше чем от метрики First Input Delay.
Парочка интересных кейсы из беты React 19, которые особо не освещались:
- Прощай useIsomorphicLayoutEffect, больше нет варнинга на сервере - https://github.com/facebook/react/pull/26395
- Если вы используете throw promise или либы с поддержкой Suspense, в рамках одного Suspense загрузка данных в параллельных компонентах начнет происходить последовательно - https://github.com/facebook/react/pull/26380
Пример кода где будет водопад запросов при использовании условного useSuspenseQuery:
В релизе очень порадовало улучшение ошибок гидрации, и централизованная обработка ошибок Error Boundaries.
Также я никак не пойму в какой версии удалили или удалят 421 ошибку гидрации - https://github.com/facebook/react/issues/24959#issuecomment-1317309116
Ошибка происходит при ререндере Suspense компонента, поддерево которого не завершило гидрацию, и приводит к деоптимизации - клиентский рендер вместо гидрации.
Очень легко словить такую ошибку используя useSyncExternalStore.
Потратил часы на разборы таких ошибок, а ситуация в итоге странная - ошибку выпиливают, а деоптимизация остаётся.
Наверное мне стоило более тщательно подойти к замеру разницы в перформансе при деоптимизации, может проблема и не такая значительная, раз ее просто можно заглушить?
- Прощай useIsomorphicLayoutEffect, больше нет варнинга на сервере - https://github.com/facebook/react/pull/26395
- Если вы используете throw promise или либы с поддержкой Suspense, в рамках одного Suspense загрузка данных в параллельных компонентах начнет происходить последовательно - https://github.com/facebook/react/pull/26380
Пример кода где будет водопад запросов при использовании условного useSuspenseQuery:
const Root = () => {
return <>
<Suspense>
<CmpWithUseSuspenseQuery />
<CmpWithUseSuspenseQuery />
</Suspense>
</>
}
В релизе очень порадовало улучшение ошибок гидрации, и централизованная обработка ошибок Error Boundaries.
Также я никак не пойму в какой версии удалили или удалят 421 ошибку гидрации - https://github.com/facebook/react/issues/24959#issuecomment-1317309116
Ошибка происходит при ререндере Suspense компонента, поддерево которого не завершило гидрацию, и приводит к деоптимизации - клиентский рендер вместо гидрации.
Очень легко словить такую ошибку используя useSyncExternalStore.
Потратил часы на разборы таких ошибок, а ситуация в итоге странная - ошибку выпиливают, а деоптимизация остаётся.
Наверное мне стоило более тщательно подойти к замеру разницы в перформансе при деоптимизации, может проблема и не такая значительная, раз ее просто можно заглушить?
GitHub
Remove layout effect warning on the server by rickhanlonii · Pull Request #26395 · facebook/react
Overview
This PR unfortunately removes the warning emitted when using layout effects on the server:
useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server...
This PR unfortunately removes the warning emitted when using layout effects on the server:
useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server...
👍14🔥1
Forwarded from Веб-платформа
Собрал для вас в одну папку авторов, ведущих блоги по фронтенду, веб-разработке и вокруг неё.
🔗 https://t.me/addlist/Z6Efi4jXwe9lODcy
Специально отобрал именно ламповые авторские блоги, а не перепосты ссылок, документации или тексты не про код.
Для меня это хороший способ следить за реакциями о событиях индустрии. Например, конференции проходят не так часто, и контент с них становится общедоступным не сразу. Кроме того, там обычно рассказывают о чём-то эпичном, не сиюминутном, так как формат обязывает. А вот в авторских бложиках (и комментариях к постам) можно собрать цельную картинку происходящего в рутине, и не просто факты, а именно авторскую интерпретацию и комментарии.
Надеюсь, будет полезно ✌️
@web_platform | Поддержать канал 🤝
🔗 https://t.me/addlist/Z6Efi4jXwe9lODcy
Специально отобрал именно ламповые авторские блоги, а не перепосты ссылок, документации или тексты не про код.
Для меня это хороший способ следить за реакциями о событиях индустрии. Например, конференции проходят не так часто, и контент с них становится общедоступным не сразу. Кроме того, там обычно рассказывают о чём-то эпичном, не сиюминутном, так как формат обязывает. А вот в авторских бложиках (и комментариях к постам) можно собрать цельную картинку происходящего в рутине, и не просто факты, а именно авторскую интерпретацию и комментарии.
Надеюсь, будет полезно ✌️
@web_platform | Поддержать канал 🤝
❤12👍4
Привет!
Свежий кейс отладки, вряд ли будет полезен (специфичный), но хочется его проговорить и забыть.
В Tramvai есть своя реализация микрофронтов с поддержкой Module Federation - Child Apps.
Какое-то время назад добавлял для Child Apps поддержку разделения на страницы для разных роутов, значительная часть работы заключалась в интеграции @loadable.
Так как микрофронты универсальные - используются на сервере и на клиенте, для эффективного добавления всех JS и CSS файлов на страницу при серверном рендеринге, нужно иметь список этих файлов.
Для этого мы уже использовали Module Federation ChunkCorrelationPlugin, который генерирует специальный json файл, содержащий список shared чанков, необходимых для микрофронта. Это как бы stats, но не тот stats что используется в webpack.
При использовании @loadable, нам нужно также иметь информацию о созданных через динамический импорт чанков, которую ChunkCorrelationPlugin не предоставляет.
Решил эту проблему через генерацию дополнительного
Но можно и заморочиться и через кастомный плагин самостоятельно сгенерировать единый файл, который будет содержать информацию из обоих миров.
Итак (это еще присказка), добавили @loadable и динамические чанки для отдельных страниц, но получили дублирование общего кода в каждом чанке - так как при использовании Module Federation как правило отключают splitChunks, и генерируются конкретные файлы:
- точка входа в микрофронт (в нашем случае серверная и клиентская)
- сам код микрофронта
- чанки для shared зависимостей
На самом деле добавить splitChunks оказалось можно, главное аккуратно - все shared зависимости, указанные для Module Federation, должны быть исключены из группы, в которую попадут общие модули между страницами, разделенными через @loadable - по сути все модули которые тянут async чанки.
И сам конфиг splitChunks (использует подход granular chunking)
Итак, вернемся к отладке и непосредственно багу.
У нас есть матрица тестов Child Apps для проверки совместимости разных мажорных версий хостового приложения и микрофронтов.
Что бы тестировать один и тот же код приложения и микрофронтов, используем симлинки.
По не понятной до сих пор причине один тест начал падать, и показывать какие-то странные новые чанки, собранные для конкретного микрофронта, с названием содержащим webpack_sharing_consume.
Для сборки приложения и микрофронта через @tramvai/cli можно указать параметр "resolveSymlinks: false", который прокинет соответствующее значение в конфиг вебпака - и он как раз использовался в интеграционных тестах.
Также симлинки использует yarn workspaces, через который организована работа с пакетами фреймворка в нашей монорепе.
Отладку мне сильно попортили наши настройки webpack file-system cache, которые не учитывали этот параметр, и переиспользовался кэш с любым значением флага, а я соответственно получал не стабильные результаты.
Оказалось, что для получения списка shared модулей использовался
А в splitChunks в метод проверки вхождения в группу в название модуля прилетал путь симлинки которую делал yarn (с node_modules). Пример -
Дополнительно заиспользовал пакет resolve для вычисления пути до модуля без резолва симлинки, что бы закрыть и кейсы пользователей, и в нашей монорепе.
История получилась в итоге не совсем про отладку, но раз с этого начал, добавлю вывод - при отладке сборки выключайте кэши)
Свежий кейс отладки, вряд ли будет полезен (специфичный), но хочется его проговорить и забыть.
В Tramvai есть своя реализация микрофронтов с поддержкой Module Federation - Child Apps.
Какое-то время назад добавлял для Child Apps поддержку разделения на страницы для разных роутов, значительная часть работы заключалась в интеграции @loadable.
Так как микрофронты универсальные - используются на сервере и на клиенте, для эффективного добавления всех JS и CSS файлов на страницу при серверном рендеринге, нужно иметь список этих файлов.
Для этого мы уже использовали Module Federation ChunkCorrelationPlugin, который генерирует специальный json файл, содержащий список shared чанков, необходимых для микрофронта. Это как бы stats, но не тот stats что используется в webpack.
При использовании @loadable, нам нужно также иметь информацию о созданных через динамический импорт чанков, которую ChunkCorrelationPlugin не предоставляет.
Решил эту проблему через генерацию дополнительного
stats_loadable.json
файла с помощью плагина @loadable/webpack-plugin. На сервере не проблема запросить доп. файл для каждого микрофронта, так как мы кэшируем эти запросы.Но можно и заморочиться и через кастомный плагин самостоятельно сгенерировать единый файл, который будет содержать информацию из обоих миров.
Итак (это еще присказка), добавили @loadable и динамические чанки для отдельных страниц, но получили дублирование общего кода в каждом чанке - так как при использовании Module Federation как правило отключают splitChunks, и генерируются конкретные файлы:
- точка входа в микрофронт (в нашем случае серверная и клиентская)
- сам код микрофронта
- чанки для shared зависимостей
На самом деле добавить splitChunks оказалось можно, главное аккуратно - все shared зависимости, указанные для Module Federation, должны быть исключены из группы, в которую попадут общие модули между страницами, разделенными через @loadable - по сути все модули которые тянут async чанки.
И сам конфиг splitChunks (использует подход granular chunking)
Итак, вернемся к отладке и непосредственно багу.
У нас есть матрица тестов Child Apps для проверки совместимости разных мажорных версий хостового приложения и микрофронтов.
Что бы тестировать один и тот же код приложения и микрофронтов, используем симлинки.
По не понятной до сих пор причине один тест начал падать, и показывать какие-то странные новые чанки, собранные для конкретного микрофронта, с названием содержащим webpack_sharing_consume.
Для сборки приложения и микрофронта через @tramvai/cli можно указать параметр "resolveSymlinks: false", который прокинет соответствующее значение в конфиг вебпака - и он как раз использовался в интеграционных тестах.
Также симлинки использует yarn workspaces, через который организована работа с пакетами фреймворка в нашей монорепе.
Отладку мне сильно попортили наши настройки webpack file-system cache, которые не учитывали этот параметр, и переиспользовался кэш с любым значением флага, а я соответственно получал не стабильные результаты.
Оказалось, что для получения списка shared модулей использовался
require.resolve
, который симлинки всегда резолвит, и отдает реальный путь. Пример - tramvai/packages/modules/react-query/lib/index.js
А в splitChunks в метод проверки вхождения в группу в название модуля прилетал путь симлинки которую делал yarn (с node_modules). Пример -
tramvai/node_modules/@tramvai/module-react-query/lib/index.js
Дополнительно заиспользовал пакет resolve для вычисления пути до модуля без резолва симлинки, что бы закрыть и кейсы пользователей, и в нашей монорепе.
История получилась в итоге не совсем про отладку, но раз с этого начал, добавлю вывод - при отладке сборки выключайте кэши)
tramvai.dev
Overview | tramvai
Micro frontends heavily integrated with tramvai framework
❤12👍3
Почему я люблю React и считаю его развитие последовательным, гармоничным и в целом работой крутых инженеров, которые думают о своих пользователях. Что именно сделало меня лучше как инженера. И конечно что я считаю минусами фреймворка либо экосистемы.
Начну наверное с Fiber архитектуры - а именно переход React на Fiber является фундаментом, благодаря которому возможны последующие фичи, такие как хуки и Suspense, Concurrent Rendering и Selective Hydration, React Server Components и Partial Prerendering.
Fiber открыл ряд возможностей, например:
- рендерить компоненты по отдельности и с разным приоритетом
- ставить работу на паузу и возобновлять ее
Это действительно крутое стратегическое решения для архитектуры React (много ли вещей вы продумали в своей работе на 8+ лет вперед?), и что немаловажно, нацеленное на лучший UX, так как длинные блокирующие задачи это одна из самых значимых проблем производительности веб-приложений.
Лучше про перф за меня расскажет Ден в этом докладе - https://www.youtube.com/watch?v=nLF0n9SACd4&ab_channel=JSConf.
Я встречал и критикау демок от React тимы с Suspense и конкурентным рендерингом и приоритезацией апдейтов на пользовательский ввод - что либо это не real world кейсы, либо перфа можно достичь и другими способами.
Не знаю, я тут вижу только крутые фичи у которых простая концептуальная база - разделение больших задач на более мелкие.
Можем обсудить в комментариях)
Следующий майндшифт - это хуки. Раньше у нас были для работы с состоянием и контекстом классовые компоненты (миксины я практически не застал), с относительно сложным жизненным циклом и действительно сложными механизмами для синхронизации стейта и пропсов, и отсутствием адекватного механизма для переиспользования логики (всей душой не люблю паттерн с композицией пачки HOC'ов).
Появление хуков - это естественная эволюция реакта, они можно сказать "напрашивались". На самом деле, конечно я не знал как будут выглядеть хуки до их анонса. Но именно концепция хуков очень органично вписывается в построение интерфейсов на функциональных React компонентах.
Что мы получили:
- простой цикл обновления компонента (useEffect подписка/очистка)
- и также предсказуемый (массив зависимостей)
- удобная работа со стейтом и контекстом
- нативный механизм для переиспользования логики
Но это возможно дело случая - для меня хуки сразу вписались в ментальную модель, которую я держу в голове разрабатывая на React, даже до начала их практического использования. У других разработчиков хуки вызвали отторжение. В общем как с любой другой новой технологией (из свежего это сигналы в Angular, руны в Svelte).
Также репутацию хуков подпортили попытки инкапсулировать в них бизнес-логику.
Мне кажется, кастомные хуки это про два основных кейса:
- переиспользовать UI логику, когда работаем с ref либо с DOM деревом напрямую
- переиспользовать утилитарную логику, общие кейсы работы с состоянием или жизненным циклом (например useSelector, useQuery, useToggle, useTimeout)
Начну наверное с Fiber архитектуры - а именно переход React на Fiber является фундаментом, благодаря которому возможны последующие фичи, такие как хуки и Suspense, Concurrent Rendering и Selective Hydration, React Server Components и Partial Prerendering.
Fiber открыл ряд возможностей, например:
- рендерить компоненты по отдельности и с разным приоритетом
- ставить работу на паузу и возобновлять ее
Это действительно крутое стратегическое решения для архитектуры React (много ли вещей вы продумали в своей работе на 8+ лет вперед?), и что немаловажно, нацеленное на лучший UX, так как длинные блокирующие задачи это одна из самых значимых проблем производительности веб-приложений.
Лучше про перф за меня расскажет Ден в этом докладе - https://www.youtube.com/watch?v=nLF0n9SACd4&ab_channel=JSConf.
Я встречал и критикау демок от React тимы с Suspense и конкурентным рендерингом и приоритезацией апдейтов на пользовательский ввод - что либо это не real world кейсы, либо перфа можно достичь и другими способами.
Не знаю, я тут вижу только крутые фичи у которых простая концептуальная база - разделение больших задач на более мелкие.
Можем обсудить в комментариях)
Следующий майндшифт - это хуки. Раньше у нас были для работы с состоянием и контекстом классовые компоненты (миксины я практически не застал), с относительно сложным жизненным циклом и действительно сложными механизмами для синхронизации стейта и пропсов, и отсутствием адекватного механизма для переиспользования логики (всей душой не люблю паттерн с композицией пачки HOC'ов).
Появление хуков - это естественная эволюция реакта, они можно сказать "напрашивались". На самом деле, конечно я не знал как будут выглядеть хуки до их анонса. Но именно концепция хуков очень органично вписывается в построение интерфейсов на функциональных React компонентах.
Что мы получили:
- простой цикл обновления компонента (useEffect подписка/очистка)
- и также предсказуемый (массив зависимостей)
- удобная работа со стейтом и контекстом
- нативный механизм для переиспользования логики
Но это возможно дело случая - для меня хуки сразу вписались в ментальную модель, которую я держу в голове разрабатывая на React, даже до начала их практического использования. У других разработчиков хуки вызвали отторжение. В общем как с любой другой новой технологией (из свежего это сигналы в Angular, руны в Svelte).
Также репутацию хуков подпортили попытки инкапсулировать в них бизнес-логику.
Мне кажется, кастомные хуки это про два основных кейса:
- переиспользовать UI логику, когда работаем с ref либо с DOM деревом напрямую
- переиспользовать утилитарную логику, общие кейсы работы с состоянием или жизненным циклом (например useSelector, useQuery, useToggle, useTimeout)
GitHub
GitHub - acdlite/react-fiber-architecture: A description of React's new core algorithm, React Fiber
A description of React's new core algorithm, React Fiber - acdlite/react-fiber-architecture
👍20💩7❤4🥴4
Отвлечемся поговорить про перформанс.
Наверное каждый эксперт в отладке и ускорении React приложений прошел непростой путь оптимизации на проде многоступенчатых форм или сложных таблиц, и знает все способы как избежать лишних ререндеров (которые не проблема сами по себе, но проблема в масштабе).
И каждый эксперт скорее всего не хотел бы этого знать и об этом думать.
Я согласен, что механизм обновления в React по умолчанию заставляет нас делать больше работы и предварительных оптимизаций (а дефолты это очень важно), либо не делать и потом все-таки становиться экспертами по реакт перформансу.
Это не означает, что в других фреймворках ничего не нужно оптимизировать, проблемы зачастую в пользовательском коде. Но по умолчанию - да, тормоза в React приложении поймать легче. При этом, мы практически всегда знаем что с этим делать.
Можно рассматривать эту проблему как сравнение Virtual DOM против реактивности и сигналов.
Пару ссылок про проблемы Virtual DOM:
- https://svelte.dev/blog/virtual-dom-is-pure-overhead
- https://vuejs.org/guide/extras/rendering-mechanism#compiler-informed-virtual-dom
Но не будем забывать про плюсы, то что уже обсуждали в предыдущем посте. Благодаря Virtual DOM и Fiber архитектуре, в реакте реализовыван прерываемый рендеринг, который открывает пачку крутых UX паттернов (suspense, транзишены и так далее).
Отличный обзор concurrent фич от Ивана Акулова - https://3perf.com/talks/react-concurrency/
Если говорить про другие фреймворки, интересные мысли можно почитать по ключевым словам типа Suspense на гитхабе в соответствующих проектах, например:
- https://github.com/sveltejs/svelte/issues/1736
- https://github.com/sveltejs/svelte/issues/3203#issuecomment-797346259
И мысли разные, в том числе от авторов фреймворков - что-то реализовать можно, что-то сложно, что-то не нужно.
Как и везде, серебряной пули нет, а плохие и медленные приложения писать с использованием сигналов также легко.
Также одна из важных вещей в Реакт - консистентность состояния.
Итого:
- по умолчанию в React легко написать медленный код
- это решаемая проблема, но бойлерплейт/надо думать
- текущая архитектура с Virtual DOM имеет и преимущества над другими решениями (компилируемыми / реактивными)
Добавлю еще коротко про React Forget.
Концептуально все просто - поможет оставить исходный код чистым, а продакшн код производительным.
Считаю это крутым экспериментальным проектом, надеюсь на его успех.
И это точно не менее предсказуемый инструмент чем любой compile-time фреймворк (такие мнения тоже встречал).
Наверное каждый эксперт в отладке и ускорении React приложений прошел непростой путь оптимизации на проде многоступенчатых форм или сложных таблиц, и знает все способы как избежать лишних ререндеров (которые не проблема сами по себе, но проблема в масштабе).
И каждый эксперт скорее всего не хотел бы этого знать и об этом думать.
Я согласен, что механизм обновления в React по умолчанию заставляет нас делать больше работы и предварительных оптимизаций (а дефолты это очень важно), либо не делать и потом все-таки становиться экспертами по реакт перформансу.
Это не означает, что в других фреймворках ничего не нужно оптимизировать, проблемы зачастую в пользовательском коде. Но по умолчанию - да, тормоза в React приложении поймать легче. При этом, мы практически всегда знаем что с этим делать.
Можно рассматривать эту проблему как сравнение Virtual DOM против реактивности и сигналов.
Пару ссылок про проблемы Virtual DOM:
- https://svelte.dev/blog/virtual-dom-is-pure-overhead
- https://vuejs.org/guide/extras/rendering-mechanism#compiler-informed-virtual-dom
Но не будем забывать про плюсы, то что уже обсуждали в предыдущем посте. Благодаря Virtual DOM и Fiber архитектуре, в реакте реализовыван прерываемый рендеринг, который открывает пачку крутых UX паттернов (suspense, транзишены и так далее).
Отличный обзор concurrent фич от Ивана Акулова - https://3perf.com/talks/react-concurrency/
Если говорить про другие фреймворки, интересные мысли можно почитать по ключевым словам типа Suspense на гитхабе в соответствующих проектах, например:
- https://github.com/sveltejs/svelte/issues/1736
- https://github.com/sveltejs/svelte/issues/3203#issuecomment-797346259
И мысли разные, в том числе от авторов фреймворков - что-то реализовать можно, что-то сложно, что-то не нужно.
Как и везде, серебряной пули нет, а плохие и медленные приложения писать с использованием сигналов также легко.
Также одна из важных вещей в Реакт - консистентность состояния.
Итого:
- по умолчанию в React легко написать медленный код
- это решаемая проблема, но бойлерплейт/надо думать
- текущая архитектура с Virtual DOM имеет и преимущества над другими решениями (компилируемыми / реактивными)
Добавлю еще коротко про React Forget.
Концептуально все просто - поможет оставить исходный код чистым, а продакшн код производительным.
Считаю это крутым экспериментальным проектом, надеюсь на его успех.
И это точно не менее предсказуемый инструмент чем любой compile-time фреймворк (такие мнения тоже встречал).
svelte.dev
Virtual DOM is pure overhead
Let’s retire the ‘virtual DOM is fast’ myth once and for all
👍15❤3🔥1
Про стабильность.
React одна из эталонных библиотек, с минимальным количеством ломающих изменений в публичном API.
Deprecated функционал проходит несколько мажорных версий, предупреждает команда реакта об этом и в документации, и в рантайме.
На сложные кейсы зачастую предоставляется готовый codemod.
Мне кажется что даже код десятилетней давности можно запустить на 18 реакте.
Для мета-фреймворка, без проблем получается обновлять мажорки, и поддерживать несколько мажорных версий одновременно.
Но быть early adopter не легко, на экспериментальные вещи часто не хватает документации (возможно не проблема если работаешь в Vercel) - поэтому никогда не спешим с интеграцией экспериментальных фич.
Про концепции.
Тут и про React, и про Redux, просто хочется отметить вещи, которые когда-то были для меня в новинку, заставили мыслить шире, и писать код лучше.
В первую очередь это функциональный подход. Хотя я до сих пор отношусь к ФП с интересом но одновременно со скептицизмом (всегда больше интересовали практические кейсы чем красивые концепции), такие базовые вещи как чистые функции, композиция, декларативность - положительно влияют на качество кода.
Сюда же идёт иммутабельность - насколько же до этого я писал сложный для отладки код где мутировал вложенные и вложенные объекты как попало...
Даже в реактивных стейт-менеджерах иммутабельность активно используется - это почти бесплатный дифф, и прозрачное обновление данных.
Нормализация - крутой подход к структурированию данных. Не всегда и везде нужен, но к примеру на архитектурных собесах мне часто попадаются кандидаты, кто выбирает Redux, а потом не может спроектировать оптимальную структуру стейта под задачу, и нормализация решила бы все их проблемы (может уже не в тренде?).
React одна из эталонных библиотек, с минимальным количеством ломающих изменений в публичном API.
Deprecated функционал проходит несколько мажорных версий, предупреждает команда реакта об этом и в документации, и в рантайме.
На сложные кейсы зачастую предоставляется готовый codemod.
Мне кажется что даже код десятилетней давности можно запустить на 18 реакте.
Для мета-фреймворка, без проблем получается обновлять мажорки, и поддерживать несколько мажорных версий одновременно.
Но быть early adopter не легко, на экспериментальные вещи часто не хватает документации (возможно не проблема если работаешь в Vercel) - поэтому никогда не спешим с интеграцией экспериментальных фич.
Про концепции.
Тут и про React, и про Redux, просто хочется отметить вещи, которые когда-то были для меня в новинку, заставили мыслить шире, и писать код лучше.
В первую очередь это функциональный подход. Хотя я до сих пор отношусь к ФП с интересом но одновременно со скептицизмом (всегда больше интересовали практические кейсы чем красивые концепции), такие базовые вещи как чистые функции, композиция, декларативность - положительно влияют на качество кода.
Сюда же идёт иммутабельность - насколько же до этого я писал сложный для отладки код где мутировал вложенные и вложенные объекты как попало...
Даже в реактивных стейт-менеджерах иммутабельность активно используется - это почти бесплатный дифф, и прозрачное обновление данных.
Нормализация - крутой подход к структурированию данных. Не всегда и везде нужен, но к примеру на архитектурных собесах мне часто попадаются кандидаты, кто выбирает Redux, а потом не может спроектировать оптимальную структуру стейта под задачу, и нормализация решила бы все их проблемы (может уже не в тренде?).
GitHub
Add react 19 codemods and recommend `codemod` command by DmytroHryshyn · Pull Request #320 · reactjs/react-codemod
adds 7 react 19 codemods
adds ability to run ts transfroms from react-codemod CLI
Testing
Running with react-codemod
npx react-codemod remove-context-provider <path>
npx react-codemod replac...
adds ability to run ts transfroms from react-codemod CLI
Testing
Running with react-codemod
npx react-codemod remove-context-provider <path>
npx react-codemod replac...
👍14❤5🔥1😁1
Вернёмся к фичам.
React позволяет легко реализовать классический серверный рендеринг с гидрацией.
Любой SSR имеет несколько значимых проблем:
- долгое время ответа страницы (ждём запросы, рисуем HTML)
- долгое время до интерактивности (уже видим контент но не кликабельно до загрузки всего JS)
- толстый бандл (тащим код всех компонентов)
При этом SSR актуален, имеет много плюсов, и фреймворки по разному стараются решить эти проблемы:
- потоковый рендеринг начиная с Marko.js
- await/defer в Remix
- islands architecture в Astro
- resumability в Qwik.js
Тут можно упомянуть костыль с lazy hydration для React.
И React также предлагает решения для всех этих кейсов, и решения очень интересные, эффективные и инновационные, не более спорные чем все остальные.
Что-то приходится долго ждать, что-то требует кардинально менять архитектуру - все не идеально, но отмечу что новые фичи опциональны.
Когда открыли репозиторий с дискуссиями React Working Group про 18 версию, я половину ночи провел за чтением, настолько было интересно, так как там обсуждались проблемы с которыми я сталкиваюсь на практике, и технологии которые помогут их решить.
И опять таки опираясь на фундамент архитектуры с vDOM и Fiber, фреймворк предлагает нам следующие решения:
Полноценный потоковый рендеринг - с поддержкой Suspense на сервере, теперь можно быстро отдать App Shell, дождаться асинхронных действий на сервере, и досылать разметку по мере необходимости - что позволит улучшить и метрику TTFB и LCP.
И на практике, почему это круто - во-первых это фундамент для React Server Components, во-вторых на этом основан уже упомянутый
Selective Hydration - больше не нужно загружать весь бандл для начала гидрации, фреймворк позволяет гидрировать отдельные части приложения по требованию (базовый кейс, по факту загрузки lazy компонентов).
Это позволяет сильно улучшить время до интерактивности приложения, причем радикально в комбинации со стримингом, так как на клиенте надо загрузить только код фреймворка и точку входа в приложение для начала гидрации.
Подробнее про новую SSR архитектуру в этой дискуссии и моих предыдущих постах.
Про SEO и стриминг интересно тут - https://github.com/vercel/next.js/discussions/50829
React позволяет легко реализовать классический серверный рендеринг с гидрацией.
Любой SSR имеет несколько значимых проблем:
- долгое время ответа страницы (ждём запросы, рисуем HTML)
- долгое время до интерактивности (уже видим контент но не кликабельно до загрузки всего JS)
- толстый бандл (тащим код всех компонентов)
При этом SSR актуален, имеет много плюсов, и фреймворки по разному стараются решить эти проблемы:
- потоковый рендеринг начиная с Marko.js
- await/defer в Remix
- islands architecture в Astro
- resumability в Qwik.js
Тут можно упомянуть костыль с lazy hydration для React.
И React также предлагает решения для всех этих кейсов, и решения очень интересные, эффективные и инновационные, не более спорные чем все остальные.
Что-то приходится долго ждать, что-то требует кардинально менять архитектуру - все не идеально, но отмечу что новые фичи опциональны.
Когда открыли репозиторий с дискуссиями React Working Group про 18 версию, я половину ночи провел за чтением, настолько было интересно, так как там обсуждались проблемы с которыми я сталкиваюсь на практике, и технологии которые помогут их решить.
И опять таки опираясь на фундамент архитектуры с vDOM и Fiber, фреймворк предлагает нам следующие решения:
Полноценный потоковый рендеринг - с поддержкой Suspense на сервере, теперь можно быстро отдать App Shell, дождаться асинхронных действий на сервере, и досылать разметку по мере необходимости - что позволит улучшить и метрику TTFB и LCP.
И на практике, почему это круто - во-первых это фундамент для React Server Components, во-вторых на этом основан уже упомянутый
await/defer
- что я считаю самой крутой фичей для классического SSR, который не готов полностью переходить на стриминг с RSC.Selective Hydration - больше не нужно загружать весь бандл для начала гидрации, фреймворк позволяет гидрировать отдельные части приложения по требованию (базовый кейс, по факту загрузки lazy компонентов).
Это позволяет сильно улучшить время до интерактивности приложения, причем радикально в комбинации со стримингом, так как на клиенте надо загрузить только код фреймворка и точку входа в приложение для начала гидрации.
Подробнее про новую SSR архитектуру в этой дискуссии и моих предыдущих постах.
Про SEO и стриминг интересно тут - https://github.com/vercel/next.js/discussions/50829
www.patterns.dev
Streaming Server-Side Rendering
Generate HTML to be rendered on the server in response to a user request
👍8❤🔥2🔥1
Еще более радикально улучшить ситуацию со временем ответа (да и производительностью серверов) поможет улучшить Partial Prerendering.
PPR тоже уже разбирали в канале, это экспериментальная фича именно реакта, хоть и используется и имеет документацию только в Next.js.
Позволяет получить на этапе сборки статичную часть приложения (App Shell), быстро отдать ее клиенту, красиво "вклеить" в нее динамическую часть в стриме ответа.
Ну и для радикального уменьшения клиентского кода, команда React предложила и реализовала широко обсуждаемые и осуждаемые React Server Components.
Доклад с анонсом RSC - https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components
И RFC с особенностями и деталями реализации - https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md
Единственная production ready реализация RSC - у Next.js, и так как это полноценный фреймворк со своими интерфейсами и особенностями, иногда сложно понять какие плюсы и минусы RSC относятся именно к базовой реализации в React, а не в интеграции.
Да, RSC накладывают много ограничений. Да, полная смена архитектуры.
Но разве есть более радикальный способ уменьшить количество клиентского кода, чем оставить этот код на сервере?
Из значимых альтернатив я могу назвать только Qwik.js (
еще существуют Phoenix LiveView, Rails Hotwire - но это не знакомые мне экосистемы), который предоставляет ленивую загрузку кода вплоть до каждого обработчика событий. То есть кода будет меньше только на старте.
Кстати оба подхода сильно зависят от хорошего интернет соединения, надеюсь в будущем будет больше исследований по теме, становится ли лучше жизнь у реальных пользователей, или как всегда трейдофф.
PPR тоже уже разбирали в канале, это экспериментальная фича именно реакта, хоть и используется и имеет документацию только в Next.js.
Позволяет получить на этапе сборки статичную часть приложения (App Shell), быстро отдать ее клиенту, красиво "вклеить" в нее динамическую часть в стриме ответа.
Ну и для радикального уменьшения клиентского кода, команда React предложила и реализовала широко обсуждаемые и осуждаемые React Server Components.
Доклад с анонсом RSC - https://react.dev/blog/2020/12/21/data-fetching-with-react-server-components
И RFC с особенностями и деталями реализации - https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md
Единственная production ready реализация RSC - у Next.js, и так как это полноценный фреймворк со своими интерфейсами и особенностями, иногда сложно понять какие плюсы и минусы RSC относятся именно к базовой реализации в React, а не в интеграции.
Да, RSC накладывают много ограничений. Да, полная смена архитектуры.
Но разве есть более радикальный способ уменьшить количество клиентского кода, чем оставить этот код на сервере?
Из значимых альтернатив я могу назвать только Qwik.js (
еще существуют Phoenix LiveView, Rails Hotwire - но это не знакомые мне экосистемы), который предоставляет ленивую загрузку кода вплоть до каждого обработчика событий. То есть кода будет меньше только на старте.
Кстати оба подхода сильно зависят от хорошего интернет соединения, надеюсь в будущем будет больше исследований по теме, становится ли лучше жизнь у реальных пользователей, или как всегда трейдофф.
nextjs.org
App Router: Partial Prerendering
An early look into Partial Prerendering and how it works.
👍10🔥3
Критика React или его экосистемы.
RSC концепция классная, но сложная. Сложная и в понимании, и в интеграции, особенно если говорить про миграцию большой кодовой базы.
В плане DX - есть как и сложности, так и плюсы. Мем про PHP код в React компонентах смешной, пока не понимаешь что есть люди кто действительно думают что серверные компоненты это возврат к каким-то древним временам - это не так, никто не требует писать SQL в компонентах, архитектура приложения и выбранные абстракции зависят только от вас.
RSC (в принципе это началось еще с Suspense и клиентских GraphQL библиотек) открывают все мощь паттерна render as you fetch - возможность минимальным количеством кода делать запросы рядом с компонентом, где эти данные будут использованы, и возможность писать код как бы без границ между сервером и клиентом.
Но появляются и проблемы:
- нужен мета-фреймворк который поможет избежать водопада и дублирования запросов (частично решается в 19 React)
- еще легче испортить архитектуру приложения, так как размазать логику запросов или все-таки написать напрямую этот SQL в компоненте стало проще
И тут мы плавно переходим к проблеме архитектуры React приложений в целом.
Как и любой другой фреймворк, реакт предлагает замкнуть все на себя:
- при SSR весь HTML начиная от
- мира за пределами
- все должно быть декларативно и даже такие сайд-эффекты как редирект делаем компонентами
- все что хотим переиспользовать - либо синглтон либо через React контекст
- разделение отображения и бизнес-логики на плечах разработчиков
RSC концепция классная, но сложная. Сложная и в понимании, и в интеграции, особенно если говорить про миграцию большой кодовой базы.
В плане DX - есть как и сложности, так и плюсы. Мем про PHP код в React компонентах смешной, пока не понимаешь что есть люди кто действительно думают что серверные компоненты это возврат к каким-то древним временам - это не так, никто не требует писать SQL в компонентах, архитектура приложения и выбранные абстракции зависят только от вас.
RSC (в принципе это началось еще с Suspense и клиентских GraphQL библиотек) открывают все мощь паттерна render as you fetch - возможность минимальным количеством кода делать запросы рядом с компонентом, где эти данные будут использованы, и возможность писать код как бы без границ между сервером и клиентом.
Но появляются и проблемы:
- нужен мета-фреймворк который поможет избежать водопада и дублирования запросов (частично решается в 19 React)
- еще легче испортить архитектуру приложения, так как размазать логику запросов или все-таки написать напрямую этот SQL в компоненте стало проще
И тут мы плавно переходим к проблеме архитектуры React приложений в целом.
Как и любой другой фреймворк, реакт предлагает замкнуть все на себя:
- при SSR весь HTML начиная от
<html>
тега находится у нас в рутовом компоненте- мира за пределами
render(<App />)
как будто не существует- все должно быть декларативно и даже такие сайд-эффекты как редирект делаем компонентами
- все что хотим переиспользовать - либо синглтон либо через React контекст
- разделение отображения и бизнес-логики на плечах разработчиков
Epic React
Render as you fetch (with and without suspense)
Speed up your app's loading of code/data/assets with "render as you fetch" with and without React Suspense for Data Fetching
👍15💩4
Разберем предметно почему считаю это проблемами.
Про синглтоны.
Допустим, у нас кастомный SSR (специально спустимся на уровень ниже), на сервере делаем запросы, используем полученные данные для рендеринга контента.
Кроме этого, часто нужно много других возможностей - работа с куками, заголовками и статусом ответа, мета-тегами, в общем все то что упрощают для нас мета-фреймворки.
Мой любимый пример, для работы с cookies на сервере, нужен соответствующий объект запроса. Для удобства и переиспользования логики, можно выделить отдельный сервис для работы с куками. Этому сервису потребуется объект Request.
Представим в виде псевдо-кода:
Так как запросов один сервер обрабатывает много, мы не можем сделать синглтон вида
Отдельно обсудим попозже как Next.js это все-таки реализует.
Передать созданный на запрос сервис в React компоненты не проблема - у нас уже будет код который на каждый запрос рендерит в строку некий рутовый
Но почему вообще мы должны работать с куками в компонентах? Для запросов и прочей логики приложения нам нужен механизм для сайд-эффектов - в разных фреймворках это loaders / actions / async components и так далее. Именно в таких лоадерах нам понадобится сервис.
Представим лоадер с использованием сервиса в виде псевдо-кода:
Какие тут варианты получить
Приложение растет, сервисов десятки, между ними много зависимостей - удобно ли это, гибко, масштабируется? Даст ли подходящий совет документация React?
Импорт
Я уверен, что лучшее решение для кейса это механизм Dependency Injection, что уже давно широко используется на практике в JS экосистеме, например в Nest.js и Angular.
Но в React экосистеме это не популярная тема. Из последнего интересного, твит от одного из ведущих разработчиков фреймворка, что DI в React стоит делать через "exports" в package.json - https://x.com/sebmarkbage/status/1765828741500981475 - и хотя это интересная мысль, она закрывает только часть кейсов который может закрыть полноценный DI контейнер.
Также DI позволит радикально улучшить кодовую базу мета-фреймворков - сейчас в Next.js / Remix вы можете видеть огромные файлы с лапшой кода, которые реализуют сразу множество фичей. И такие же суровые Pull Request'ы, в которых эти файлы шатают.
Любое расширение этих фреймворков со стороны тоже как правило большая боль, потому что официальных точек расширения нет, помимо ручной конфигурации сборщика.
Даже без DI, пример фреймворка с плагинной архитектурой, у которых с расширением дела гораздо лучше - Nuxt.js.
Можно сравнить любой плагин, например PWA, для Next и для Nuxt:
- у Некста можно пошатать конфиг, обернуть рутовый компонент
- у Nuxt можно вклиниться на разные этапы жизненного цикла приложения, модифицировать и параметры билд тайма, и рантайма
Про синглтоны.
Допустим, у нас кастомный SSR (специально спустимся на уровень ниже), на сервере делаем запросы, используем полученные данные для рендеринга контента.
Кроме этого, часто нужно много других возможностей - работа с куками, заголовками и статусом ответа, мета-тегами, в общем все то что упрощают для нас мета-фреймворки.
Мой любимый пример, для работы с cookies на сервере, нужен соответствующий объект запроса. Для удобства и переиспользования логики, можно выделить отдельный сервис для работы с куками. Этому сервису потребуется объект Request.
Представим в виде псевдо-кода:
class Cookies {
constructor(request) {}
get() {}
set() {}
}
Так как запросов один сервер обрабатывает много, мы не можем сделать синглтон вида
export const cookies = new Cookies(request)
- request будет доступен только в обработчике запроса, экземпляр сервиса надо создавать каждый раз новый.Отдельно обсудим попозже как Next.js это все-таки реализует.
Передать созданный на запрос сервис в React компоненты не проблема - у нас уже будет код который на каждый запрос рендерит в строку некий рутовый
<App />
компонент, и инстанс cookies
можно передать через контекст.Но почему вообще мы должны работать с куками в компонентах? Для запросов и прочей логики приложения нам нужен механизм для сайд-эффектов - в разных фреймворках это loaders / actions / async components и так далее. Именно в таких лоадерах нам понадобится сервис.
Представим лоадер с использованием сервиса в виде псевдо-кода:
async function loader(params) {
return api.getSomething().then((response) => {
cookies.set(foo, response.someUniqueField)
return response
})
}
Какие тут варианты получить
api
или cookies
кроме импорта синглтона? Только интеграция с нашим SSR, и например передавать их в аргументы лоадера - const { api, cookies } = params
в данном случае (например так позволяет сделать Remix).Приложение растет, сервисов десятки, между ними много зависимостей - удобно ли это, гибко, масштабируется? Даст ли подходящий совет документация React?
Импорт
cookies
и headers
в Next.js возможен только за счет использования Async Local Storage - и это очень подходящее использование технологии, но по аналогии с этими объектами, сможете ли вы как-то просто добавить свои сервисы для Next.js приложения?Я уверен, что лучшее решение для кейса это механизм Dependency Injection, что уже давно широко используется на практике в JS экосистеме, например в Nest.js и Angular.
Но в React экосистеме это не популярная тема. Из последнего интересного, твит от одного из ведущих разработчиков фреймворка, что DI в React стоит делать через "exports" в package.json - https://x.com/sebmarkbage/status/1765828741500981475 - и хотя это интересная мысль, она закрывает только часть кейсов который может закрыть полноценный DI контейнер.
Также DI позволит радикально улучшить кодовую базу мета-фреймворков - сейчас в Next.js / Remix вы можете видеть огромные файлы с лапшой кода, которые реализуют сразу множество фичей. И такие же суровые Pull Request'ы, в которых эти файлы шатают.
Любое расширение этих фреймворков со стороны тоже как правило большая боль, потому что официальных точек расширения нет, помимо ручной конфигурации сборщика.
Даже без DI, пример фреймворка с плагинной архитектурой, у которых с расширением дела гораздо лучше - Nuxt.js.
Можно сравнить любой плагин, например PWA, для Next и для Nuxt:
- у Некста можно пошатать конфиг, обернуть рутовый компонент
- у Nuxt можно вклиниться на разные этапы жизненного цикла приложения, модифицировать и параметры билд тайма, и рантайма
X (formerly Twitter)
Sebastian Markbåge (@sebmarkbage) on X
This is also how most dependency injection in React apps should be done. Not Context.
Import a named default value from JS. Override it in package.json imports.
https://t.co/taaFQW5vG3
Import a named default value from JS. Override it in package.json imports.
https://t.co/taaFQW5vG3
👍14❤1
По поводу мира вне наших компонентов.
Для SPA-приложений, самый простой кейс это запросы, критичные для отрисовки страницы. Для SSR тоже валидно если говорим про кастомную реализацию, мета-фреймворки все-таки решают кейс давая механизм для загрузки данных под конкретный роут.
Стандартный паттерн - делать запросы в
Не логично, потому что запрос за данными не обязан быть привязан к жизненному циклу конкретного компонента.
Он даже не обязан быть привязан к конкретному роуту приложения, но по роутам проще всего группировать сайд-эффекты, которые мы хотим запустить параллельно и которые важны для отрисовки.
Не эффективно, так как слишком поздно. Зачем ждать полный цикл рендера и отрисовки в браузере до старта запроса?
На конкретном примере, очень многие приложения используют React Router. До относительно свежей версии 6.4, этот роутер вообще не давал никаких инструментов для загрузки данных. Столкнулся с этим два года назад для пет проекта, удивился, не долго думая добавил костыль с загрузкой через тот же useEffect но c возможностью привязать запрос к компоненту страницы.
Поэтому для React Router появление механизма loader'ов для SPA-приложений это уже большой шаг вперед - в том числе для новых разработчиков, они будут видеть что запрос можно запустить где-то вне useEffect, и это нормально.
Это касается не только запросов, в компонентах лучше по максимуму ничего не делать кроме непосредственно отображения и обработки пользовательских событий.
Как пример, приведу список отдельных модулей для фреймворка Tramvai.
Там все не идеально, такие модули как router / render / server связаны сильнее чем хотелось бы.
Но в любом случае по списку наглядно что все реализовано по отдельности, за счет модульной архитектуры:
- рендеринг и гидрация
- роутинг
- обработка серверных запросов и инициализация сервера
- работа с SEO
- логгер / метрики / куки / client-hints
- интеграция с React Query отдельным модулем
И работать со всем этим мы также можем по отдельности.
Например, используя React Query, сбросить или обновить кэш на любом этапе жизненного цикла запроса за страницей, начиная от механизма экшенов, или сделать префетч конкретной query до рендеринга страницы, а потом использовать ее в компоненте.
Для SPA-приложений, самый простой кейс это запросы, критичные для отрисовки страницы. Для SSR тоже валидно если говорим про кастомную реализацию, мета-фреймворки все-таки решают кейс давая механизм для загрузки данных под конкретный роут.
Стандартный паттерн - делать запросы в
useEffect
. Но это не эффективно, не всегда логично, и не всегда хорошо для UX и перформанса.Не логично, потому что запрос за данными не обязан быть привязан к жизненному циклу конкретного компонента.
Он даже не обязан быть привязан к конкретному роуту приложения, но по роутам проще всего группировать сайд-эффекты, которые мы хотим запустить параллельно и которые важны для отрисовки.
Не эффективно, так как слишком поздно. Зачем ждать полный цикл рендера и отрисовки в браузере до старта запроса?
На конкретном примере, очень многие приложения используют React Router. До относительно свежей версии 6.4, этот роутер вообще не давал никаких инструментов для загрузки данных. Столкнулся с этим два года назад для пет проекта, удивился, не долго думая добавил костыль с загрузкой через тот же useEffect но c возможностью привязать запрос к компоненту страницы.
Поэтому для React Router появление механизма loader'ов для SPA-приложений это уже большой шаг вперед - в том числе для новых разработчиков, они будут видеть что запрос можно запустить где-то вне useEffect, и это нормально.
Это касается не только запросов, в компонентах лучше по максимуму ничего не делать кроме непосредственно отображения и обработки пользовательских событий.
Как пример, приведу список отдельных модулей для фреймворка Tramvai.
Там все не идеально, такие модули как router / render / server связаны сильнее чем хотелось бы.
Но в любом случае по списку наглядно что все реализовано по отдельности, за счет модульной архитектуры:
- рендеринг и гидрация
- роутинг
- обработка серверных запросов и инициализация сервера
- работа с SEO
- логгер / метрики / куки / client-hints
- интеграция с React Query отдельным модулем
И работать со всем этим мы также можем по отдельности.
Например, используя React Query, сбросить или обновить кэш на любом этапе жизненного цикла запроса за страницей, начиная от механизма экшенов, или сделать префетч конкретной query до рендеринга страницы, а потом использовать ее в компоненте.
GitHub
feat: add route loader component · SuperOleg39/mind-basket@8cd3a38
Contribute to SuperOleg39/mind-basket development by creating an account on GitHub.
👍9
Про "все-в-React" или "все-в-компоненте" и декларативность.
Что мы видели интересного в коде компонентов:
-
-
-
-
- не могу не вспомнить
Компоненты и хуки из списка объединяет то, что это либо обернутые в компонент сайд-эффекты, либо размазанная по компонентам логика, которой не возможно управлять централизованно.
Это не примеры абсолютного зла - так как плюсы без сомнения есть, в очень гибком роутинге, либо в умном механизме запросов без бойлерплейта, либо в минималистичной абстракции логики фреймворка от пользователя.
Но в масштабе, проблемы есть.
Хранить список роутов в компонентах - не масштабируется, подход с заранее объявленными роутами гораздо легче развивать и поддерживать.
Но декларативность в React мире доходит до того, что например в React Router вообще нет такой цельной сущности как Router! Есть только набор хуков, а сделать что-то с роутингом вне компонентов мы просто не имеем возможности.
Например (императивно) создать
Отсутствует жизненный цикл, настолько RR связан с реактом. Механизм loader'ов добавил хотя бы один этап жизненного цикла. Но для какой-нибудь авторизации разработчики все-равно будут создавать всякие
Наглядный пример в этой статье - https://blog.logrocket.com/authentication-react-router-v6/. Хороший гайд, все аккуратно, react way, но насколько же сильно размазана аутентификация по компонентам, и не существует снаружи.
Просто сравните с Angular, где через DI предоставляется отдельный сервис для аутентификации, используемый и в UI и в гуарде роутера.
React Query большие молодцы, выделяют логику в отдельные сущности, и с ними можно работать где угодно, например QueryClient. Такие вещи очень упрощают интеграцию для SSR фреймворка.
Но и тут есть проблемы, возьмем сами квери. Как отдельной сущности их просто нет, есть набор параметров вида const query = useQuery({ queryKey: ['todos'], queryFn: getTodos }), и только через queryKey возможна связь для одной и той же квери между использованием в компоненте и прямой работой через QueryClient в других местах.
Сложно делать расширяемые и переиспользуемые query, параметры считываются и сохраняются сразу при рендере хука - нельзя сделать
Используя React Query и React Hook Form, можно делать хороший UX и писать мало кода на сложные кейсы, но очень сложно явно выделить сущности / модели / бизнес-логику, что опять-таки может выстрелить в ногу в масштабе, и уж точно не поможет сделать архитектуру "кричащей".
По поводу таких кейсов как
Реакт или Ремикс не дает нам условный
Нам предлагается явно рендерить нужный скрипт в компоненте (прямо или косвенно). И хотя для RSC и стриминга понятно почему важно поддержать эти механизмы, выбранный путь мне совершенно не нравится.
Если подвести итог, эта псевдо-декларативность в React и экосистеме, как и явно обозначенная ограниченная область ответственности React (не фреймворк, а библиотека!), напрямую влияет на то как сообщество пишет приложения, на образ мышления новых разработчиков, и как мне кажется влияет негативно.
Получается для меня, главная проблема React это точно не лишние ререндеры, а архитектурные вопросы. И вряд ли экосистема в этом плане заметно поменяется, а область ответственности React только расширяется. Скорее всего в будущем при поддержке RSC и прочих современных возможностей, остро встанет вопрос что мета-фреймворк либо будет написан в react way стиле, либо останется в стороне.
Что мы видели интересного в коде компонентов:
-
<Route>
и <Redirect>
из React Router-
<Script>
из Next.js и <Scripts>
из Remix-
useQuery
из React Query или Apollo-
useForm
из React Hook Form- не могу не вспомнить
<FormSpy>
из FormikКомпоненты и хуки из списка объединяет то, что это либо обернутые в компонент сайд-эффекты, либо размазанная по компонентам логика, которой не возможно управлять централизованно.
Это не примеры абсолютного зла - так как плюсы без сомнения есть, в очень гибком роутинге, либо в умном механизме запросов без бойлерплейта, либо в минималистичной абстракции логики фреймворка от пользователя.
Но в масштабе, проблемы есть.
Хранить список роутов в компонентах - не масштабируется, подход с заранее объявленными роутами гораздо легче развивать и поддерживать.
Но декларативность в React мире доходит до того, что например в React Router вообще нет такой цельной сущности как Router! Есть только набор хуков, а сделать что-то с роутингом вне компонентов мы просто не имеем возможности.
Например (императивно) создать
const router = new Router()
, предзаполнить router.addRoute(...)
, в лоадере/экшены выполнить router.redirect(..)
, передать в <Router.Provider router={router} >
и так далее.Отсутствует жизненный цикл, настолько RR связан с реактом. Механизм loader'ов добавил хотя бы один этап жизненного цикла. Но для какой-нибудь авторизации разработчики все-равно будут создавать всякие
<Auth>
и <ProtectedRoute>
компоненты.Наглядный пример в этой статье - https://blog.logrocket.com/authentication-react-router-v6/. Хороший гайд, все аккуратно, react way, но насколько же сильно размазана аутентификация по компонентам, и не существует снаружи.
Просто сравните с Angular, где через DI предоставляется отдельный сервис для аутентификации, используемый и в UI и в гуарде роутера.
React Query большие молодцы, выделяют логику в отдельные сущности, и с ними можно работать где угодно, например QueryClient. Такие вещи очень упрощают интеграцию для SSR фреймворка.
Но и тут есть проблемы, возьмем сами квери. Как отдельной сущности их просто нет, есть набор параметров вида const query = useQuery({ queryKey: ['todos'], queryFn: getTodos }), и только через queryKey возможна связь для одной и той же квери между использованием в компоненте и прямой работой через QueryClient в других местах.
Сложно делать расширяемые и переиспользуемые query, параметры считываются и сохраняются сразу при рендере хука - нельзя сделать
queryKey
функцией, на момент рендера useQuery
надо иметь все параметры для формирования массива ключей.Используя React Query и React Hook Form, можно делать хороший UX и писать мало кода на сложные кейсы, но очень сложно явно выделить сущности / модели / бизнес-логику, что опять-таки может выстрелить в ногу в масштабе, и уж точно не поможет сделать архитектуру "кричащей".
По поводу таких кейсов как
Scripts
в Remix, или поддержка метаданных и стилей в React 19.Реакт или Ремикс не дает нам условный
AssetsManager
, в который мы смогли бы добавить ресурс явно по конкретному условию, например:if (analyticsEnabled) {
assetManager.addScript({ src: anaylicsScript, async })
}
Нам предлагается явно рендерить нужный скрипт в компоненте (прямо или косвенно). И хотя для RSC и стриминга понятно почему важно поддержать эти механизмы, выбранный путь мне совершенно не нравится.
Если подвести итог, эта псевдо-декларативность в React и экосистеме, как и явно обозначенная ограниченная область ответственности React (не фреймворк, а библиотека!), напрямую влияет на то как сообщество пишет приложения, на образ мышления новых разработчиков, и как мне кажется влияет негативно.
Получается для меня, главная проблема React это точно не лишние ререндеры, а архитектурные вопросы. И вряд ли экосистема в этом плане заметно поменяется, а область ответственности React только расширяется. Скорее всего в будущем при поддержке RSC и прочих современных возможностей, остро встанет вопрос что мета-фреймворк либо будет написан в react way стиле, либо останется в стороне.
GitHub
Add useRouter Hook · Issue #6430 · remix-run/react-router
Hi guys, at current React Conf Hooks have been announced for React v16.7. What's the plan for supporting them? Will there be a useRouter() any time soon? https://reactjs.org/docs/hooks-faq.html...
🔥22❤1👍1