Это перевод поста Эндрю Кларка о выходе столь ожидаемой версии React. Оригинальный пост в блоге React.
Мы с удовольствием сообщаем о выходе React v16.0! Среди изменений некоторые давно ожидаемые нововведения, например фрагменты, обработка ошибок (error boundaries), порталы, поддержка произвольных DOM-атрибутов, улучшения в серверном рендере, и уменьшенный размер файла.
Новые типы для рендера: фрагменты и строки
Теперь вы можете вернуть массив элементов из render
-метода компонента. Как и с другими массивами, вам надо добавлять ключ к каждому элементу, чтобы реакт не ругнулся варнингом:
render() {
// Нет необходимости оборачивать в дополнительный элемент!
return [
// Не забудьте добавить ключи :)
<li key="A">Первый элемент</li>,
<li key="B">Второй элемент</li>,
<li key="C">Третий элемент</li>,
];
}
В будущем мы, вероятно, добавим специальный синтакс для вывода фрагментов, который не будет требовать явного указания ключей.
Мы добавили поддержку и для возврата строк:
render() {
return 'Мама, смотри, нет лишних спанов!';
}
Полный список поддерживаемых типов.
Улучшенная обработка ошибок
Ранее ошибки рендера во время исполнения могли полностью сломать ваше приложение, и описание ошибок часто было малоинформативным, а выход из такой ситуации был только в перезагрузке страницы. Для решения этой проблемы React 16 использует более надёжный подход. По-умолчанию, если ошибка появилась внутри рендера компонента или в lifecycle-методе, всё дерево компонентов отмонтируется от корневого узла. Это позволяет избежать отображения неправильных данных. Тем не менее, это не очень дружелюбный для пользователей вариант.
Вместо отмонтирования всего приложения при каждой ошибке, вы можете использовать error boundaries. Это специальные компоненты, которые перехватывают ошибки в своём поддереве и позволяют вывести резервный UI. Воспринимайте их как try-catch операторы, но для React-компонентов.
Для дальнейшей информации проверьте наш недавний пост про обработку ошибок в React 16.
Порталы
Порталы дают удобный способ рендера дочерних компонентов в DOM-узел, который находится за пределами дерева родительского компонента.
render() {
// React не создаёт новый див. Он рендерит дочерние элементы в domNode.
// domNode - это любой валидный DOM-узел,
// вне зависимости от его расположения в DOM-дереве.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}
Полный пример находится в документации по порталам.
Улучшенный серверный рендеринг
React 16 содержит полностью переписанный серверный рендерер, и он действительно быстрый. Он поддерживает стриминг, так что вы можете быстрее начинать отправлять байты клиенту. И благодаря новой стратегии сборки, которая убирает из кода обращения к process.env
(хотите верьте, хотите — нет, но чтение process.env
в Node очень медленное!), вам больше не надо бандлить React для получения хорошей производительности серверного рендеринга.
Ключевой разработчик Саша Айкин написал замечательную статью, рассказывающую об улучшениях SSR в React 16. Согласно Сашиным бенчмаркам, серверный рендеринг в React 16 примерно в 3 раза быстрее, чем в React 15. При сравнении с рендером в React 15 c убранным из кода process.env
получается ускорение в 2.4 раза в Node 4, около 3-х раз в Node 6 и аж в 3.8 раза в Node 8.4. А если вы сравните с React 15 без компиляции (без убранного process.env
), React 16 оказывается на порядок быстрее в последней версии Node! (Как Саша указал, к этим синтетическим бенчмаркам надо относиться осторожно, так как они могут не отображать производительность реальных приложений).
Более того, React 16 лучше в восстановлении отрендеренного на сервере HTML, когда последний приходит в браузер. Больше не требуется начальный рендер, используемый для проверки результатов с сервера. Вместо этого он будет пытаться переиспользовать как можно больше существующего DOM. Больше не будут использоваться контрольные суммы! В целом мы не рекомендуем рендерить на клиенте отличающийся от сервера контент, но это может быть полезно в некоторых сценариях (например вывод меток времени).
Больше в документации по ReactDOMServer.
Поддержка произвольных DOM-атрибутов
Вместо игнорирования неизвестных HTML и SVG атрибутов, теперь React будет просто передавать их в DOM. Вдобавок это позволяет нам отказаться от длиннющих списков разрешённых атрибутов, что уменьшает размер бандла.
Уменьшенный размер файла
Несмотря на все эти нововведения, React 16 меньше, чем React 15.6.1!
react
весит 5.3 kb (2.2 kb gzipped), по сравнению с 20.7 kb (6.9 kb gzipped) ранее.react-dom
весит 103.7 kb (32.6 kb gzipped), по сравнению с 141 kb (42.9 kb gzipped) ранее.react
+react-dom
вместе 109 kb (34.8 kb gzipped), по сравнению 161.7 kb (49.8 kb gzipped) ранее.
В общей сложности размер уменьшился на 32% по сравнению с прошлой версией (30% после gzip-сжатия).
На уменьшение размера частично влияют изменения в сборке. React теперь использует Rollup для создания "плоских" бандлов (по-видимому Эндрю Кларк тут имел ввиду "scope hoisting", что давно было в Rollup, но в Webpack появилось только в третьей версии) всех поддерживаемых форматов, что привело к выигрышу и в размере и в скорости работы. Также плоский формат бандла приводит к тому, что воздействие React на бандл приложения остаётся одинаковым, независимо от того, как вы доставляете свой код конечным пользователям, напр. используя Webpack, Browserify, уже собранные UMD-модули или любой другой способ.
MIT лицензия
Если вы вдруг пропустили, React 16 теперь доступен под MIT лицензией. А для тех, кто не может обновиться немедленно, мы выложили версию React 15.6.2 под MIT.
Новая архитектура ядра
React 16 — это первая версия React, построенная на основе новой архитектуры, называемой Fiber. Вы можете почитать всё об этом проекте в инженерном блоге Facebook. (Спойлер: мы полностью переписали React!)
Fiber затрагивает большинство новых фич в React 16, такие как error boundaries или фрагменты. Через несколько релизов вы увидите несколько новых фич, так как мы будем постепенно раскрывать потенциал React.
Наверно наиболее впечатляющее нововведение, над которым мы работаем — это асинхронный рендеринг, позволяющий компонентам использовать кооперативную многозадачность в рамках рендера через периодическую передачу управления браузеру. Итог — с асинхронным рендерингом приложения более отзывчивы, так как React не блокирует главный поток.
Следующее демо даёт нам взглянуть на суть проблемы, решаемую асинхронным рендерингом (подсказка: обратите внимание на крутящийся черный квадрат).
Ever wonder what "async rendering" means? Here's a demo of how to coordinate an async React tree with non-React work https://t.co/3snoahB3uV pic.twitter.com/egQ988gBjR
— Andrew Clark (@acdlite) September 18, 2017
Мы думаем, что асинхронный рендеринг — очень важная вещь, двигающая React в будущее. Чтобы сделать переход на v16.0 как можно более безболезненным, мы пока не включили какие-либо асинхронные фичи, но мы рады выкатить их в ближайшие месяцы. Следите за обновлениями!
Установка
React v16.0.0 доступен в npm репозитории.
Для установки React 16 используя Yarn:
yarn add react@^16.0.0 react-dom@^16.0.0
Для установки React 16 используя npm:
npm install --save react@^16.0.0 react-dom@^16.0.0
Мы также предоставляем UMD-вариант, выложенный на CDN:
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
Ссылка на документацию по детальным инструкциям по установке.
Переход со старой версии
Хотя React 16 включает значительные внутренние изменения, в случае обновления вы можете относится к этому релизу как к любому обычному мажорному релизу React. Мы используем React 16 в Facebook и Messenger.com с начала этого года, мы выкатили несколько бета-версий и релиз кандидатов, чтобы максимально исключить возможные проблемы. Если не учитывать некоторые нюансы, то ваше приложение должно работать с 16-й версией, если с 15.6 оно работало без каких-либо варнингов.
Устаревшие методы
Восстановление отрендеренного на сервере кода теперь имеет явное API. Для восстановления HTML вам надо использовать ReactDOM.hydrate
вместо ReactDOM.render
. Продолжайте использовать ReactDOM.render
, если вы рендерите только на клиентской стороне.
React Addons
Как ранее было объявлено, мы прекращаем поддержку React Addons. Мы ожидаем, что последняя версия каждого дополнения (кромеreact-addons-perf
; см. ниже) будет работоспособна в ближайшем будущем, но мы не будем публиковать новых обновлений.
По ссылке есть ранее опубликованные предложения по миграции.
А react-addons-perf
вообще не будет работать в React 16. Скорее всего мы выпустим новую версию этого инструмента в будущем. А пока вы можете использовать браузерные инструменты для измерения производительности.
Несовместимые изменения
React 16 включает несколько небольших изменений без обратной совместимости. Они влияют на редко используемые сценарии и затронут малую часть приложений.
- React 15 имел ограниченную и недокументированную поддержку error boundaries через использование
unstable_handleError
. Этот метод теперь переименован вcomponentDidCatch
. Вы можете использовать codemod для автоматической миграции на новое API. ReactDOM.render
иReactDOM.unstable_renderIntoContainer
теперь возвращают null, если вызваны из lifecycle-метода. Вместо этого теперь используйте порталы или ссылки.setState
:
- Вызов
setState
с null больше не будет вызывать реконсиляцию. Это позволит определять в коллбеке надо ли вызывать перерендер. - Вызов
setState
напрямую в рендере всегда вызывает реконсиляцию, чего раньше не было. Независимо от того, вам однозначно не надо вызывать setState из метода рендера. - Коллбек
setState
'а (второй аргумент) теперь вызывается немедленно послеcomponentDidMount
/componentDidUpdate
, вместо того чтобы ждать полного рендера дерева компонентов.
- Вызов
- При замене
<A />
на<B />
,B.componentWillMount
будет вызываться всегда передA.componentWillUnmount
. РанееA.componentWillUnmount
мог вызываться раньше в некоторых случаях. - Ранее изменение ссылки на компонент вызывало всегда обнуление ссылки перед вызовом рендера. Теперь мы изменяем ссылку позднее, когда применяем изменения к DOM.
- Небезопасно вызывать рендер контейнера, если содержимое было изменено в обход React. Ранее это работало в некоторых случаях, но никогда не поддерживалось. Мы не вызываем варнинг в этом случае. Вместо этого вы сами должны чистить дерево вашего компонента используя
ReactDOM.unmountComponentAtNode
. Посмотрите на этот пример. - Lifecycle-метод
componentDidUpdate
больше не получает параметрprevContext
. (см.#8631) - Shallow рендерер больше не вызывает
componentDidUpdate
, т.к. ссылки на DOM недоступны. Это изменение делает его консистентным с методомcomponentDidMount
(который тоже не вызывался в предыдущих версиях). - Shallow рендерер больше не имеет метода
unstable_batchedUpdates
.
Сборка
- Теперь недоступны
react/lib/*
иreact-dom/lib/*
. Даже для CommonJS окружений, React и ReactDOM теперь собраны в отдельные файлы (“flat bundles”). Если ваш проект ранее зависел от недокументированных внутренних возможностей React'а и они больше не работают, дайте нам об этом знать в новом тикете, а мы постараемся придумать способ миграции для вас. - Больше нет билда
react-with-addons.js
. Все аддоны из этого билда уже опубликованы по отдельность в npm и имеют однофайловые браузерные версии, если они нужны вам. - Методы и возможности, помеченные устаревшими в 15.x, теперь убраны из основного пакета.
React.createClass
теперь доступен какcreate-react-class
,React.PropTypes
какprop-types
,React.DOM
какreact-dom-factories
,react-addons-test-utils
какreact-dom/test-utils
, а shallow рендерер какreact-test-renderer/shallow
. См. посты в блоге 15.5.0 и 15.6.0 для инструкций по миграции кода и автоматических кодемодов. - Имя и путь до однофайловых браузерных билдов изменились для подчёркивания различий между разработческими и боевыми билдами. Например:
react/dist/react.js
>react/umd/react.development.js
react/dist/react.min.js
>react/umd/react.production.min.js
react-dom/dist/react-dom.js
>react-dom/umd/react-dom.development.js
react-dom/dist/react-dom.min
.js >react-dom/umd/react-dom.production.min.js
Требования к JavaScript-окружению:
React 16 зависит от коллекций Map и Set. Если вы поддерживаете старые браузеры и устройства, в которых нет этого нативно (напр. IE < 11), используйте полифилы, такие как core-js или babel-polyfill.
Окружение с полифилами для React 16 используя core-js для поддержки старых браузеров может выглядеть как-то так:
import 'core-js/es6/map';
import 'core-js/es6/set';
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
React также требует requestAnimationFrame
(даже в тестовых средах). Простая заглушка для тестовых окружений может выглядеть так:
global.requestAnimationFrame = function(callback) {
setTimeout(callback, 0);
};
Благодарности
Как обычно, этот релиз был бы невозможен без наших контрибьюторов-волонтёров (open source contributors). Спасибо всем, кто заводил баги, открывал пулл-реквесты, отвечал в тикетах, писал документацию.
Отдельное спасибо нашим корневым контрибьюторам, особенно за их героические усилия в последние несколько недель пререлизного цикла: Brandon Dail, Jason Quense, Nathan Hunzaker, и Sasha Aickin.
Комментарии (29)
Akuma
28.09.2017 21:10Пакет prop-types «15.6.0 is the latest of 17 releases».
Будет 16.0.0 версия или все без проблем работает с этой?
Честно говоря, забивал на варнинги о deprecated :)dagen Автор
28.09.2017 21:47С тех пор, как prop-types ушёл в
мир инойсвой отдельный репозитарий, его версии не совпадали с версиями React. Не знаю точно политики Facebook, но похоже на то, что этот пакет был сделан только для безболезненного удаления его из кодовой базы реакта (эдакие юзер-френдли breaking changes).
У нас с 16-й версией вроде как работает (причём не с 15.6.0, а с какой-то более старой версией), но его почти не осталось — везде перешли на flow-аннотации. Чего и вам советую :)Akuma
28.09.2017 21:50Понятно. Почитаю что это такое :) Оно ведь? flow.org
dagen Автор
28.09.2017 22:23+1Оно) Раз вы не читали про Flow, то рекомендую перевод товарища m1rko: habrahabr.ru/post/326394
maxfarseer
29.09.2017 10:57Как именно используете flow внутри react-компонентов? Мы используем babel-flow-plugin-proptypes
dagen Автор
29.09.2017 12:02Мы вообще отказались от propTypes. У нас практически везде functional stateless components, в этом случае пропсы приходят как аргумент компонента-функции, соответственно мы просто пишем аннотацию:
const SomeComponent = (props: SomeComponentProps) => {}
В случае stateful c наследованием от обычного React.Component используем стандартный синтаксис flow:
class PromoList extends React.Component<PromoListProps> {}
Нигде не преобразовываем в prop-types, поэтому отключили соответствующее правило в eslint-плагине react/prop-types.
Leopotam
29.09.2017 11:16Или сразу на typescript — можно будет получать сообщения мгновенно через language server, а не постпроцессом на сохранение файла.
dagen Автор
29.09.2017 12:10Не работал с typescript, не могу сказать как он в сравнении с flow, но последний тоже использует language server и позволяет получать сообщения об ошибках прямо во время написания кода.
Leopotam
29.09.2017 12:43Ну тогда они проделали хорошую работу за последний год, раньше flow работал только под osx и только как обработка сохраненного файла. Но в случае с ts мы можем избавиться от babel-я (а в случае с flow мы принуждаем к его использованию) и гнать код напрямую в нужный таргет (включая jsx и декораторы) и в нужном формате (commonjs, umd). Так же получаем приятные необязательные вещи типа контрактов-интерфейсов, модификаторов доступа и enum-ов.
dagen Автор
29.09.2017 14:33Flow очень быстро развивается сейчас, много изменений, фиксов и релизов. А с декораторами да, беда у babel, поддержка актуальной спеки декораторов появилась только в babel@7, который пока бета.
А остальное тоже есть, просто ts использует встроенные возможности, а в случае c flow надо использовать россыпь инструментов (что нисколько не пугает, видимо потому, что привык уже). Интерфейсы были давно, точно больше года назад, да и энамы тоже. И модификаторы доступа появились во flow, кстати, пусть и недавно :)
Надеюсь это не превратится в холивар, а останется обменом мнениями между двумя коллегами :) И я с удовольствием попробую typescript в живом коммерческом проекте, если представиться такая возможность. Как и на dart вернуться было бы интересно, просто для саморазвития.Leopotam
29.09.2017 15:24Россыпь — это утомительно для настройки нескольких проектов. :( Раньше использовали jsdoc + webstorm на бэкенде, переехали на ts + vscode — пока только один позитив как по фичам, так и по скорости работы. Про холивар — а есть тема для него? То, что есть конкуренция — так это замечательно, стимулирует развитие всех продуктов :)
Kalifriki
30.09.2017 00:22Ни модификаторов доступа, ни перечислений во Flow на данный момент нет.
dagen Автор
30.09.2017 10:41Да, вы правы, грешным делом подумал, что уже имплементировали private полностью (впрочем к protected ещё не приступали вообще). А для private есть только поля, но не методы. По методам ждут соответствующего предложения в TC39, которое пока на stage-2, а поля уже в stage-3 и появились в flow@0.54.0 (последняя версия — 0.56.0).
Есть ещё read-only (и write-only модификаторы вдобавок) в интерфейсах, которые воспроизводят функционал ключевого слова final.
А перечисления живут и здравствуют:
const countries = { US: "United States", IT: "Italy", FR: "France" }; type Country = $Keys<typeof countries>; const italy: Country = 'IT'; const nope: Country = 'nope'; // ERROR TROWN
faiwer
29.09.2017 13:35Обновился. Пока что споткнулся только на двух одинаковых моментах:
- события вроде
onClick
не воспринимаютfalse
какnull
илиundefined
. Т.е. если было что-то вродеonClick={someBoolean && this.onClick}
, то теперь придётся как-нибудь иначе написать. Например такonClick={somBoolean ? this.onClick : null}
. render
метод вreactDOM
точно таким же образом реагирует наcallback
. В моём случае я проглядел этот момент, пока тестировал в dev-сборке, т.к. у меня былоdev && someMethod
. И поймал я эту ошибку уже в минифицированной версии. Что характерно, теперь ошибки на продакшне идут по номерам и не содержат внятного текста. Его можно получить перейдя по такой вот примерно ссылке. Возможно так и раньше было, но я столкнулся впервые.
В остальном вроде всё работает, как работало.
WebProd
29.09.2017 15:32Его можно получить перейдя по такой вот примерно ссылке. Возможно так и раньше было, но я столкнулся впервые.
Так было и раньше
Akuma
02.10.2017 10:43А
onClick={someBoolean && this.onClick}
у вас точно раньше работало?
Чисто теоретически оно вообще не должно работать, если React не будет самостоятельно разбирать выражение. Ведь там вместо биндинга функции происходит просто выполнение логического выражения. Или я что-то не понимаю.
Всегда пользовался вторым вариантом и даже как-то не задумывался.faiwer
02.10.2017 10:50Работало. И сейчас работает, только warning-и кидает. А что именно вас смущает? Всё что выполняется в
{}
всегда выполняется, это же просто JavaScript код (посмотрите итоговый js-code иReact.createElement
). Или вы проthis
внутриthis.onClick
? Если проthis
, то я использую такую нотацию:
class MyComponent extends React.PureComputed { onClick = evt => { // some code } // code }
Что примерно эквивалентно:
constructor() { // some code this.onClick = evt => { /* code */ }; }
- события вроде
inoyakaigor
29.09.2017 16:53Удивительное дело — Реакт 16 вышел несколько дней назад. Казалось бы — большое событие во фронтэнде и во всех тематических чатах в телеге уже это обсудили, а вот статей на больших ресурсах на эту тему кроме этой до сих пор не было.
dagen Автор
29.09.2017 21:12Это настолько большое событие, что об этом трезвонить начали чуть ли не за год на всех фейсбучных конференциях. У меня до сих пор слайды для коллег остались (сделанные по конспекту доклада того же Эндрю Кларка), которые почти полностью повторяют эту статью. Как и в issues на гитхабе реакта многие эти вещи уже обсуждались по много раз. Так что когда я выкладывал этот перевод, вполне допускал мысль, что меня заминусуют с комментариями: «ну и чО ты тут навыкладывал? это давно всем известно уже» :)
Akuma
02.10.2017 10:45А подскажите как его подружить с react-hot-loader?
С версией 1.х пишут что не работает (и не работает). Попробовал 3-ю бету — та же ошибка.
Второй версии вроде как не существует.
Или я отстал от жизни и сейчас hot-reload для Реакта делается иначе?
Anarions
Не совсем понимаю про requestAnimationFrame
Odrin
У React 16 есть зависимость от requestAnimationFrame. В NodeJS окружении этой функции нет и для нее необходимо написать заглушку.
Anarions
Но это необходимо только на серверной стороне и в тестовых окружениях?
dagen Автор
Почему, в любых окружениях, где это не реализовано. Например в старых браузерах.