Иван Акулов про разработку


Гео и язык канала: Россия, Русский
Категория: Технологии


JS · React · веб-перформанс · разработка и архитектура
Экс: https://x.com/iamakulov
По всем вопросам (рекламу не продаю): @iamakulov
Чатик канала: @iamakulov_channel_chat

Связанные каналы  |  Похожие каналы

Гео и язык канала
Россия, Русский
Категория
Технологии
Статистика
Фильтр публикаций


🇬🇪 11 апреля (пт) буду в Тбилиси — и выступлю на Tbilisi JS! Расскажу про оптимизации, которые стреляют в ноги: как CDN может сделать сайт медленнее или как font-display: swap может ухудшить Core Web Vitals.

Приходите на митап, если вы в Тби :) Там ещё тройка крутых докладов.

https://t.me/tbilisi_js/355


Привет! Написал новый пост, где реверс-инжинирю минификатор из 2004-го: https://3perf.com/blog/packer/

(Минификатор, внезапно, поддерживает ES2015+ и сжимает код компактнее, чем Terser?!)


Чёрт, снова вышел длинный пост вместо ссылок


Компонент выше:
— говорит «запускай view transition», когда рендерится
— бросает исключение-промис, что заставляет Реакт ждать
— когда скриншот готов, резолвит промис, что позволяет Реакту закончить рендер

Если поместить этот компонент в конец дерева, то можно запустить view transition в конце рендера. Прямо как с useLayoutEffect, но асинхронно.

Попробуем это решение во Framer в течение недель. Расскажу, как сработает 🙂




5) Если хуки не подходят, почему бы не запускать view transition перед началом рендера? Так можно, и Framer делает именно так. Но это создаёт другую проблему: рендер большой страницы может занять секунды, и всё это время страница будет заморожена.

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

Воркэраунд — эмулировать асинхронный useLayoutEffect с помощью :


4) Удобное место, чтобы сказать «запускай view transition» — это хук useLayoutEffect. Этот хук вызывается, когда React уже закончил рендерить новую страницу, но браузер всё ещё показывает старую. Если запустить view transitions отсюда, то страница будет заморожена буквально долю секунды.

Идеально, да? Нет. «Запускай view transition» — это асинхронный процесс, а useLayoutEffect не поддерживает асинхронность (и вряд ли будет, исходя из архитектуры Реакта).


View Transitions, React и перформанс
Ну и давайте про челленджи поговорим. Из недавнего — я узнал, что view transitions фризят страницу, и это влияет на перформанс-метрики, которые собирает Google 🫠

Следите за руками:

1) View Transitions — это новый стандарт, с помощью которого можно анимировать переходы между страницами. Framer поддерживает view transitions, и часть сайтов (в том числе framer.com) их используют

2) Переходы между страницами у Framer-сайтов работают как в любом реакт-приложении: вы нажимаете ссылку, а мы рендерим новую страницу Реактом. Это может быть медленно, поэтому, чтобы не фризить вкладку, мы оборачиваем рендер в startTransition(). (Вот как это работает)

Тут придёт Никита Прокопов и скажет, что вообще-то Реакт не нужен, и новую страницу будет гораздо быстрее отрендерить, просто взяв HTML с сервера. Это правда (но, кстати, не всегда — мы измерили!), и мы работаем над этим, но backwards compatibility и бэклог — сложно 😶‍🌫️

3) Проблемы начинаются, когда переходы между страницами анимируются с помощью view transitions. Вот как выглядит переход в этом случае:
— Вы говорите браузеру «запускай view transition»
— Браузер делает скриншот страницы и замораживает её целиком
— Вы рендерите новую страницу (реактом/вью/чем угодно)
— Вы говорите браузеру «всё, новая страница готова»
— Браузер анимирует переход

Заморозка страницы — это неприятно. Но ещё неприятнее то, что она совсем не встраивается в жизненный цикл Реакта.

4.7k 1 19 51 13

💻 Воркшоп (нет, канал я оживил не только для этого анонса): в марте пройдёт двенадцатый воркшоп по Реакт-перформансу! В консалтинге я помогал компаниям ускорять Реакт в 2-4 раза; в воркшопе я учу всему, чему сам научился в процессе.

Как выглядит воркшоп: мы берём медленное приложение → дебажим его в Chrome DevTools и React Profiler → находим и чиним боттлнек → повторяем для всех типичных перформанс-антипаттернов. В процессе мы смотрим на дорогие рендеры, гидрацию, startTransition(), перевычисления стилей и ещё пачку всего.

Приходите, цены на билеты вырастут с первого марта :) Отзывы и расписание вот тут → https://fwdays.com/en/event/react-performance-workshop-3


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

🙋 Как дела: я закрыл консалтинг! Последние шесть лет я зарабатывал, консультируя компании (Google, CNBC, Toggl и кучу других) про веб-перформанс. Но в 2023-м мой консалтинг-драйв выгорел, и с 2024-го я ушёл в найм. Просуммировал выводы и уроки тут: https://iamakulov.com/notes/lessons-from-self-employment/

(Теперь я лидю веб-перформанс во Framer.com — это no-code-платформа для создания сайтов. Сайты на Реакте, поэтому перформанс-челленджи интересные :D)


​​Show all events (линк)
Добавляет кучу дополнительных данных в перформанс-трейс. Включаю, когда хочется больше деталей про то, что именно происходит в браузере.

Бонус: если скопировать название ивента и вставить его в https://source.chromium.org/search, то этот сайт покажет, где именно в коде Chromium происходит этот ивент. Полезно, если нужно прям закопаться в поведение браузера


​​Две самые любимые:

Event initiators (линк)
Добавляет стрелочки между setTimeout() и его колбеком, requestAnimationFrame() и его колбеком, и т.п. Супер-удобно, держу включённым по умолчанию:


Собрал несколько незадокументированных фич в девтулзах, которые упрощают отладку перформанса: https://github.com/iamakulov/devtools-perf-features


​​(Кстати, новый раунд React-перформанс-воркшопа начнётся уже на следующей неделе. Приходите: https://fwdays.com/en/event/react-rerformance-workshop)


Но для перформанса кайф Immer в следующем:

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

То есть, если взять userReducer с картинки выше и вызвать его с таким экшеном:

const user = { id: 123, status: { kind: 'online' } }
const action = {
type: 'UPDATE_USER_STATUS',
payload: { statusKind: 'online' }
}
const newUser = userReducer(user, action)

то Immer просто вернёт тот же объект user, который он получил:

console.log(newUser === user) // → true

Почему это круто? Прелставьте, что сервер три раза подряд прислал один и тот же статус пользователя.
— С обычным редьюсером объекты user и user.status поменяются три раза. Если в приложении есть компоненты, подписанные на эти объекты, то эти компоненты тоже перерендерятся три раза.
— С Immer-редьюсером объекты user и user.status поменяются максимум один раз. Компоненты, подписанные на них, тоже перерендерятся один раз.

Это круто, потому что что это убирает ненужные ререндеры.

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


​​Как Immer помогает с перформансом
Immer удобно использовать в Redux-редьюсерах, Jotai-атомах и т.д. С ним код получается компактнее + защищённее от случайных мутаций:


Что такое Immer (пропускайте, если знаете)
Immer — это библиотека для пэтчинга объектов. Например, у вас есть объект pokemon:

const pokemon = {
ownerId: 557,
details: { kind: 'Pikachu' },
evolutionHistory: []
}

и вы хотите сгенерировать новый объект с другим kind и evolutionHistory. В обычном JS это будет выглядеть длинно:

const evolvedPokemon = {
...pokemon,
details: {
...pokemon.details,
kind: 'Raichu',
},
evolutionHistory: [
...evolutionHistory,
{ from: 'Pikachu', evolvedAt: new Date() }
]
}

А с Immer это будет выглядеть гораздо компактнее:

const evolvedPokemon = immer.produce(pokemon, draft => {
draft.details.kind = 'Raichu'
draft.evolutionHistory.push({ from: 'Pikachu', evolvedAt: new Date() })
})

Короче, с Immer вы пишете код, который меняет объект напрямую — а Immer отслеживает все изменения и вместо того, чтобы менять старый объект, возвращает новый. (Под капотом это работает с помощью JS Proxies.)


В воркшопе про React-перформанс у меня есть несколько любимых малоизвестных способов ускорить приложение.

Один из них — это переделать useSelector() в useStore() в Redux. Про него я писал в кейс-стади Causal: https://3perf.com/blog/causal/#useselector-vs-usestore

Другой — это подключить Immer в Redux-редьюсеры, Jotai-атомы и прочие места. Вот в чём фишка.


А что у вас
Опрос
  •   Реакт 18
  •   Реакт 17
  •   Реакт 16 🥲
  •   Не Реакт, Реакт не нужен
1185 голосов


React Concurrency под капотом

Наконец-то опубликовался доклад, с которым я выступал осенью на Smashing Conf и performance.​now(). Ловите :) https://3perf.com/talks/react-concurrency/

Внутри — куча картинок про новые перфоманс-фичи React 18:
⚛️ Как именно работает useTransition() (с демкой в девтулзах и просмотром кода из React-а, да)
🧊 Как применять , чтобы ускорить гидрацию, и как его не применять
🫠 Почему Vue.js и Preact отказались реализовывать что-то похожее на React Concurrency

(и другое)

Показано 20 последних публикаций.