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


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


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

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

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


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


Компонент выше:
— говорит «запускай 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»
— Браузер делает скриншот страницы и замораживает её целиком
— Вы рендерите новую страницу (реактом/вью/чем угодно)
— Вы говорите браузеру «всё, новая страница готова»
— Браузер анимирует переход

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


💻 Воркшоп (нет, канал я оживил не только для этого анонса): в марте пройдёт двенадцатый воркшоп по Реакт-перформансу! В консалтинге я помогал компаниям ускорять Реакт в 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 🥲
  •   Не Реакт, Реакт не нужен
1166 голосов


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

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

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

(и другое)


🖼️

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



загрузит 500.jpg на обычном экране и 1000.jpg на 2×-ретина-экране.

Чтобы эта фича работала хорошо, обычно также нужно указать ширину картинки в атрибуте sizes. Без sizes браузер будет считать, что картинка — шириной во всё окно браузера. Из-за этого будет загружаться слишком большой файл.

Но правильный sizes прописать сложно. Если на десктопе картинка шириной в 500 пикселей, а на мобильном — в 80% экрана, атрибут придётся сделать таким: sizes="(min-width: 768px) 500px, 80vw". Если условий больше, то атрибут будет ещё сложнее.

***

Так вот. Оказывается, Chromium начал экспериментировать с . С таким атрибутом браузер будет рассчитывать ширину автоматически; сложные условия в sizes больше прописывать будет не нужно.

Но — такой атрибут работает только для изображений с loading="lazy".

Почему?
— Браузер не может рассчитать ширину изображения (и выбрать подходящий файл из srcset) без CSS
— Но — браузер не ждёт загрузки CSS, чтобы начать загружать изображения

Загрузки стилей ждут только изображения с loading="lazy". (Это из-за технических причин — без стилей браузер не может понять, во вьюпорте ли картинка и нужно ли её скачивать.) Поэтому и оптимизируются только они.

🔗 Ссылки:
— GitHub-ишью с изменением в стандарты: https://github.com/whatwg/html/pull/8008
— Chromium-баг: https://bugs.chromium.org/p/chromium/issues/detail?id=1359051


Мой самый любимый дискавери из этого кейса — то, что веб-воркеры могут не только ускорить, но и замедлить приложение. [Веб-воркеры — это фича, с помощью которой можно взять код и вынести его в отдельный поток.]

У веб-воркеров много ограничений, но самое интересное — это то, что данные между главным потоком и веб-воркером всегда копируются (за парой исключений). Если данных много, то и копирование будет долгим.

У Causal в главном потоке было дорогое вычисление — парсинг данных (пик ↑). Мы попробовали вынести этот парсинг в веб-воркер, но вместо 800 мс на парсинг браузер начал тратить 4000-6000 мс на копирование данных.

¯\_(ツ)_/¯

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