React 16: огляд нової архітектури fiber
Я Шемет Євген — професійно займаюсь розробкою більше 10-ти років, виступаю на конференціях, організовую IT-мітапи, викладаю у благодійній фундації BrainBasket та з цього навчального року у ВНТУ. Я доповідав на Vinnytsia.js про React 16 . І нещодавно мене попросили написати на цю тему статтю для DOU. Статтю пишу вперше, тому буду радий вашим зауваженням та порадам в особистих повідомленнях або коментарях.
Fiber
Fiber — це нова архітектура, що покладена в основу React 16, реліз якого відбувся нещодавно . Велика частина коду була переписана з нуля. Основною метою було створення можливості для пріоритизації оновлень вмісту. Також переписана система обробки помилок та усунуті деякі старі незручності, наприклад, необхідність обгортати декілька елементів в один кореневий елемент. Існуюче API, на щастя, майже не зачепили.
Демо
Розпочати знайомство найкраще з проблеми, яку має вирішити нова версія. Її добре видно на демо. Демо синтетичне, симулює щось накшталт екрана диспетчерської таксі. На карті міста жовті машини. На машинах чорні числа, що вказують, скільки машині ще їхати до точки призначення. Числа змінюються кожної секунди, і під час цієї зміни, анімація підлагує. Лаги з'єднання являються того, що виконуються дві паралельні дії: обробка анімації та оновлення DOM. Анімація працює добре, аж до поки не стається масове оновлення DOM.
Щоб проблему було краще видно, введена штучна затримка, не забувайте що демо синтетичне. Але проблема цілком реальна: поки переобраховується DOM, анімація не програється, через ті що всі ресурси покладено на роботу з DOM. І в рамках старої архітектури React цю проблему не можна було вирішити аж ніяк. Треба віддати належно розробникам бібліотеки: вони, зіткнувшись з цією проблемою, переписали значно частину коду. Незважаючи на це, міграція не має викликати великі труднощі.
Забігаючи наперед, скажу, що, якщо натиснути на чекбокс зверху, демо перемкнутися у режим fiberі перестане гальмувати. До речі, не залишайте демо надовго на самоті, бо воно казиться.
Наразі
React для забезпечення високої швидкості роботи використовує технологію Vitrual DOM. У пам'яті підтримується спрощена копія DOM, де за вузлами закріплені конкретні екземпляри(instance) компонентів, що ними керують. Коли змінюється стан примірника, відбувається процес оновлення, що складається з таких етапів:
- Компоненти опитуються щодо змін.
- DOM в пам'яті перебудовується.
- Обраховується різниця з реальним деревом DOM та вносяться безпосередні зміни.
Минулі версії React для оновлення використовували алгоритм, що його заднім числом назвали Stack. І в нього з часом виявився значний недолік: він працює пробачимо пошуком в глибину, і його робота неперервна. А оскільки в браузері все виконується в одному потоці, то під час оновлення інші процеси мають зачекати. У випадку високопріоритетних}, наприклад анімації, це може стати відчутним клопотом.
Новий алгоритм оновлення
Архітектура fiberназвана на честь алгоритмом, що лежить в її основі. Алгоритм полягає у розбитті процесу оновлення на дві фазі:
- Фаза узгодження(reconciliation) — коли виконуються переобрахунки компонентів і відбувається оновлення DOM у пам'яті.
- Фаза внеску(commit) — коли виконується безпосереднє оновлення DOM.
Варто зазначити, що фазу узгодження(reconciliation) можна переривати. fiberза допомогою requestIdleCallback просити у браузера виділити годину, коли тій не буде завантажений роботою. При зворотньому виклику браузер вказує, скільки, власне, у нього є вільного часу. Це дає змогу fiber-у планувати частину оновлень на цей проміжок. Якщо браузер не підтримує requestIdleCallback, то React робить поліфіл(polyfill).
Алгоритм fiberу свою чергу назв на честь найменшого об'єкта, що лежить в його основі. За кожним еземпляром (компонента чи елемента) закріплений такий об'єкт, що контролює його стан та зв'язок з іншими компонентами.
{ stateNode return child sibling parent }
Процес оновлення виглядає таким чином
У нас є поточне(current) дерево компонентів та елементів, сформоване за допомогою об'єктів fiber. Стрілочки вниз це child, вгору parent, вправо sibling.
Створюється паралельне робітничо(workInProgress) дерево, що частково складається зі старого дерева.
Визначаються компоненти, що мають зміни (позначені зірочками).
Дерево поступово розгортається, і на його основі відбудовується нове дерево. Там де є оновлення — клонуються елементи і вносяться зміни. Там де оновлень немає — використовуються наявні елементи.
В результаті формується внесок(pending commit). Що для застосування очікує вже більшого проміжку часу. Тому що фазу внескупереривати не можна.
Після того як відбувається внесок(commit), поточне(current) дерево не знищується. Для економії часу дерева просто міняються місцями. Це називається подвійна буферизація(double buffering).
Застосування
Для того щоб відчути нові можливості, необхідно застосувати режим відкладених оновленьReactDOM.unstable_deferredUpdates. (Всі експериментальні можливості спочатку поставляються з префіксом unstable_).
tick() { ReactDOM.unstable_deferredUpdates(() => ( this.setState((prevState) => ({ tick: prevState.tick + 1 })) )) }
Оновлення, що відбуваються в рамках deferredUpdates, проходять паралельно.
Зверніть увагу:
- Необхідно застосовувати setState зі зворотним викликом(callback), setState з об'єднання об'єктом стає застарілим(deprecated).
- Якщо новий стан буде залежати від поточного стану, то необхідно використовувати параметр зворотнього виклику prevState замість this.state. Тому що він може бути викликаний декілька разів.
Порівняння
Жовтим позначені — оновлення, фіолетовим — анімації, червоним — лаги.
Stack:
Fiber:
Як бачите, обробка CSS анімацій не зупиняється навіть при високій завантаженості оновленнями DOM.
Також
Разом з новою архітектурою при переписуванні React були виправлені деякі невеликі архітектурні помилки.
Фрагменти (Fragments)
Відтепер, якщо компонент повертає набір елементів, його не обов'язково обгортати в один корінний елемент. Ви можете повертати масив елементів, що дуже зручно в місцях, де неможливо просто обгорнути елементи в <div>. Наприклад, у роботі з таблицями і списками, якщо компонент має повернути декілька рядків або елементів списку. Також тепер можна повертати стрічки.
const TableHeader = () => { return [ <tr><th colspan="2">Автомобіль</th><th colspan="2">Водій</th></tr>, <tr><th>Номер</th><th>Марка</th><th>Позивний</th><th>Телефон</th></tr>, ] }
Кордони помилок (Error boundaries)
Запроваджена нова система обробки помилок. Тепер, якщо в компоненті виникає помилка, можна застосувати метод життєвого циклу componentDidCatch.
class Map extends React.Component { constructor(props) { super(props) this.state = { hasError: false } } componentDidCatch(error, info) { this.setState(() => { hasError: true }) } render() { if (this.state.hasError) { return <h1>На жаль, залишилася прикра помилка.</h1> } return <MapContent />; } }
Портали (Portals)
Іноді виникає необхідність створити елемент не в рамках поточної ієрархії, а приєднати, наприклад, як у випадку з модальними вікнами, до <body>. На допомогу приходять портали.
render() { return ReactDOM.createPortal(<Modal />, domElement) }
Атрибути (Attributes)
React 16 дозволяє вам використовувати власні атрибути.
<div hello="world" />
Будьте обережні. Це означає, що фільтрація атрибутів більше не виконується.
<div myData="[Object object]" />
Тім не менш, атрибути, що мають канонічне ім'я, все одне валідуються. І ви отримаєте попередження, якщо використовуєте неправильне ім'я атрибута.
// Warning: Invalid DOM property `tabindex`. Did you mean `tabIndex`? <div tabindex="-1" />
Майбутнє
Потенціал нової архітектури реалізовано не повністю. І у розробників є багато планів на майбутнє, що стали реальними завдяки fiber.
Пріоритизація
Оновлення всередині вже пріоритизуються, але цей процес ще далекий від ідеалу. Крім того, очікується більше контролю над цим процесом. Пріоритети:
- Synchronous — синхронний, виконується одночасно;
- Task — завдання до наступного тіку(tick);
- Animation — анімація до наступного кадру(frame);
- High — високий;
- Low — низький;
- Offscreen/Hidden — схований або поза межами екрана.
Превізуалізація (pre-rendering)
Оскільки візуалізація розбита на дві фазі, то можна чітко визначити, коли все необхідне завантажене, обраховане і готове до відображення. В майбутньому це відкриває шлях до потокового завантаження превізуалізованих на сервері компонентів. Також це має спростити етап завантаження великих аплікацій.
Абстракція
Мабуть, ви знаєте, що React наразі працює на великій кількості платформ. Наприклад:
- Браузер: react-dom
- Мобільні: react-native
- Термінал: react-blessed
- Віртуальна реальність: aframe-react
- Arduino: react-hardware
Команда React активно працює над тим, щоб зробити React незалежним від оточення. З версії v0.14 ReactDOM був виділений в окремий пакет. З версії v0.16 розробникі рапортують, що React ставши (майже :)) повністю незалежний від браузера.
Проблеми
Коли запускається наступне оновлення, а минуле ще не обраховане до кінця, то старе оновлення припиняється і його проміжний результат відкидається. Якщо таке стається регулярно, виникає ситуація, коли дані на сторінці оновлюються нерегулярно або зовсім не оновлюються. Така ситуація називається голодуванняstarvation. Симулювати це можна збільшивши затримку при оновленні у демо.
Міграція
Колі
Вже.
Життєвий цикл компонента
Будьте уважні та обережні, якщо ви використовуєте відкладені оновлення. Деякі методи життєвого циклу під час одного оновлення можуть викликатись двічі або більше разів. Пов'язаність язано це з тим, що оновлення може бути відкладене через більш нагальні оновлення, а потім переобраховане. Це методи фазі узгодження(reconciliation):
- componentWillMount
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
Методи фазі внеску(commit), викликаються тільки один раз:
- componentDidMount
- componentDidUpdate
- componentWillUnmount
Помилки
Відтепер, з введенням кордонів помилок, React у випадку помилки не намагається продовжувати роботу. Це покладається на ваші плечі. Якщо є необроблена помилка, що доходити до верхнього рівня, дерево компонентів повністю перемонтується.
Атрибути
React більше не видаляє незнайомі атрибути, і вам бажано (але не обов'зково) це робити самим.
setState
Виклик увазі setState({ key: value }) вважається застарілим(deprecated). Використовуйте setState зі зворотним викликом(callback).
Матеріали та ресурси
Посилання
Ресурси
- Lin Clark — A Cartoon Intro to Fiber — React Conf 2017
- Keynote — Andrew Clark aka @acdlite at @ReactEurope 2017
- A tiny Fiber renderer — Dustan Kasten, React London 2017
Опубліковано: 29/09/17 @ 10:52
Розділ Різне
Рекомендуємо:
DOU Labs: як харків'яни створили IT-музей
Кібербезпека по-українськи: про тиск силовиків, білих і чорних хакерів і цінності диванних експертів
Junior дайджест: курси, стажування, інтернатура. Жовтень'17
Кейс: від 0 до 13000 чоловік в місяць для сайту клініки лазерної медицини
Віддалена робота: плюси і менеджерські особливості