Disclamer
Это вольный перевод оригинальной статьи из официального блога. Почему вольный? Скорее потому, что в оригинале слишком много воды и отсылок к причинам тех или иных принятых в прошлом решений.
Ничего нового?
Семнадцатый релиз React необычен отсутствием новых фич и/или функционала. Этот релиз сфокусирован на добавлении возможности постепенного обновления проектов на следующие мажорные версии библиотеки в будущем, что актуально для проектов с большой кодовой базой.
Многообещающий Concurrent Mode не будет представлен в 17 версии, как и другие нововведения, над которыми активно работает команда. Грядущий релиз является частью стратегии постепенных (частичных) обновлений.
Постепенные обновления
В течение последних 7 лет обновления React были в духе "все или ничего". Либо обновляемся до новой версии, либо остаемся на старой. Порой, необходимо было менять что-то в кодовой базе, как, например будет с устаревшим Context API, который не получится перенести автоматически.
React 17 привносит стратегию постепенных обновлений. Команда чинит большое количество имеющихся проблем в React 17. Это позволяет в следующей версии библиотеки иметь больше опций при обновлении.
Первый из вариантов будет таким же как и прежде: обновляем все приложение разом. Но теперь будет опция обновить приложение по частям. Например, большую часть приложения легко перетащить на новую версию (React 18, или React 19), тогда как какое-нибудь загруженное через lazy-подход диалоговое окно или часть приложения под определенном роутом можно будет оставить на React 17.
Команда приготовила репозиторий, который демонстрирует как загружать асинхронно (lazy-load) более старые версии React. В примере используется приложение на React 17.0.0-rc.0, которое подгружает компоненты, написанные с помощью устаревших приемов, работающие на React 16.8
Список изменений в React 17
Изменение в делегировании событий
Реакт с самой его первой версии меняет способ привязки событий, например onClick
, к DOM-элементам. Реакт автоматически использует прием делегирования событий и привязывает все события к объекту document
. Таким образом, достигается повышение производительности.
Однако, в случае, если несколько версий React используется на странице, у вас микрофронтенды или все еще часть функционала работает на jQuery, возникают проблемы. Такое поведение ломает event.stopPropagation()
: если вложенное дерево остановило распространение (propagation) события, внешнее дерево все равно получит его. Это делает сложным работу в случае вложенных нескольких версий React. Команда популярного редактора Atom столкнулась с такой проблемой.
Теперь все обработчики крепятся к корневому элементу, а не объекту document:
const rootNode = document.getElementById('root'); // <-- вот сюда
ReactDOM.render(<App />, rootNode);
Убран костыль с Синтетическим Событием (SyntheticEvent Even Pooling)
В 17-м Реакте убрана оптимизация событий, которая более не актуальна в современных браузерах.
function handleChange(event) {
// это работает в 16 React только если добавить event.persist()
setData(data => ({
...data,
// This crashes in React 16 and earlier:
text: event.target.value
}));
}
Теперь такой код не будет валиться с ошибкой, и нет нужды писать event.persist()
Для обратной совместимости эта функция оставлена в качестве заглушки. Разработчики попробовали это на существующем коде в Facebook и увидели отсутствие регрессий. Возможно, это обновление еще и исправило какое-то количество багов!
Ближе к браузерам
Было сделано некоторое количество небольших изменений. Эти изменения сближают React с поведением браузера.
Событие
onScroll
больше не всплывает, чтобы избежать текущей путаницы;События
onFocus
иonBlur
изменены "под капотом" на нативныеfocusin
иfocusout
;onClickCapture
и другие Capture-события теперь используют браузерные обработчики событий.
useEffect() теперь полностью асинхронный
useEffect(() => {
// This is the effect itself.
return () => {
// This is its cleanup.
};
});
Если раньше только функция эффекта запускалась асинхронно, тогда как возвращаемая функция для очистки подписок запускалась синхронно, так же как и метод componentWillUnmount()
, то теперь и эта функция работает асинхронно, что улучшает производительность в случае перерисовки большого количества элементов, например, когда на сайте присутствуют вкладки, или при переходе со страницы на страницу.
Для синхронной работы, можно по-прежнему использовать useLayoutEffect()
, который остался незатронутым.
Ошибки при возвращении undefined
Это изменение довольно минорно, но повышает консистентность ошибок.
Если раньше нельзя было возвращать undefined
только в обычных компонентах, то теперь такая ошибка будет выбрасываться еще и в React.forwardRef
и React.memo
.
let Button = forwardRef(() => {
// We forgot to write return, so this component returns undefined.
// React 17 surfaces this as an error instead of ignoring it.
<button />;
});
let Button = memo(() => {
// We forgot to write return, so this component returns undefined.
// React 17 surfaces this as an error instead of ignoring it.
<button />;
});
Улучшенный стек вызовов при ошибках
Когда выбрасывается ошибка, браузер показывает stack trace с названиями функций и их местоположения. К сожалению, довольно трудно по этому стеку вызовов понять, в каком месте и в каком компоненте была вызвана ошибка на самом деле.
Хочется узнать, что не просто компонент Button
вызывал ошибку, но и в каком месте дерева React-компонентов эта кнопка находилась.
В 17-м React используется новых механизм по генерации стека вызовов, что позволяет увидеть дерево React-компонентов, которое привело к ошибке, даже в production-среде.
Удаление приватных экспортов
Последнее изменение — это удаление некоторых внутренних экспортов, которые ранее были открыты наружу. Например, React Native for Web ранее зависела на некоторых внутренностях системы событий, но эта зависимость не была надежной.
В 17-м React эти приватные экспорты были удалены. По большей части из-за того, что только вышеупомянутый проект их использовал, а миграция на более надежные методы уже была произведена.
Также был удалены ReactTestUtils.SimulateNative
методы. Они не были документированы, но теперь их не будет вовсе.
Changelog
Переводить каждый пункт в логе изменений не имеет смысла, множество из них слишком минорны, а об основных было рассказано выше.
В новой версии React также включено 5 изменений в React, 37 изменений в React Dom, пару изменений в React DOM Server, одно изменение в React Test Rerender.
А что с Concurrent Mode?
По-прежнему, этот режим имеет статус экспериментального. В 17-м React было исправлено множество багов, удалены одни unstable_
методы, и добавлены новые. Пока для продакшена использовать его рано, но потыкать определенно можно и нужно. Например, есть библиотека для работы с Firebase, reactfire, разработчики которой сделали основную версию зависимой от Concurrent Mode. К сожалению, репозиторий кажется заброшенным последние несколько месяцев. Надеюсь, это исправится.