React Labs: Над чим ми працювали - березень 2023

22 березня 2023 року від Джозефа Савона, Джоша Сторі, Лорен Тен, Менгді Чен, Самуеля Сусла, Сатьї Гунасекаран, Себастьяна Маркбоге та Ендрю Кларка


У постах React Labs ми пишемо про проекти, які перебувають в активній стадії дослідження та розробки. Ми досягли значного прогресу в них з часу нашого останнього оновлення, і ми хотіли б поділитися тим, що ми дізналися.


Компоненти сервера React

React Server Components (або RSC) - це нова архітектура застосунків, розроблена командою React.

Ми вперше поділилися нашими дослідженнями щодо RSC у вступній розмові та RFC. Щоб нагадати про них, ми представляємо новий тип компонентів - серверні компоненти, які запускаються заздалегідь і виключаються з вашого пакета JavaScript. Серверні компоненти можуть запускатися під час збірки, дозволяючи вам читати з файлової системи або отримувати статичний вміст. Вони також можуть працювати на сервері, надаючи вам доступ до рівня даних без необхідності створювати API. Ви можете передавати дані за допомогою пропсів із серверних компонентів до інтерактивних клієнтських компонентів у браузері.

RSC поєднує просту ментальну модель "запит/відповідь" багатосторінкових програм, орієнтованих на сервер, із бездоганною інтерактивністю односторінкових програм, орієнтованих на клієнта, надаючи вам найкраще з обох світів.

З часу нашого останнього оновлення ми об'єднали Компоненти сервера React RFC для затвердження пропозиції. Ми вирішили невирішені питання з пропозицією React Server Module Conventions і досягли консенсусу з нашими партнерами щодо використання конвенції "use client". Ці документи також є специфікацією того, що має підтримувати RSC-сумісна реалізація.

Найбільшою зміною є те, що ми ввели async / await як основний спосіб отримання даних з серверних компонентів. Ми також плануємо підтримувати завантаження даних з клієнта, додавши новий хук use, який розгортає обіцянки. Хоча ми не можемо підтримувати async / await у довільних компонентах у клієнтських програмах, ми плануємо додати його підтримку, коли ви структуруєте клієнтську програму подібно до того, як структуровано програми RSC.

Тепер, коли ми досить добре розібралися з отриманням даних, ми досліджуємо інший напрямок: надсилання даних від клієнта на сервер, щоб ви могли виконувати мутації бази даних та реалізовувати форми. Ми робимо це, дозволяючи вам передавати функції серверної дії через межу між сервером і клієнтом, які потім може викликати клієнт, забезпечуючи безперебійний RPC. Серверні дії також надають вам поступово вдосконалені форми перед завантаженням JavaScript.

Компоненти сервера React постачаються у Маршрутизаторі застосунків Next.js. Це демонструє глибоку інтеграцію маршрутизатора, який дійсно купується в RSC як примітив, але це не єдиний спосіб побудувати RSC-сумісний маршрутизатор та фреймворк. Існує чіткий поділ на функції, що надаються специфікацією RSC та реалізацією. Компоненти сервера React - це специфікація для компонентів, які працюють у сумісних фреймворках React.

Зазвичай ми рекомендуємо використовувати існуючий фреймворк, але якщо вам потрібно створити свій власний фреймворк, це можливо. Створити власний RSC-сумісний фреймворк не так просто, як хотілося б, головним чином через необхідність глибокої інтеграції з пакувальниками. Поточне покоління бандлерів чудово підходить для використання на клієнті, але вони не були розроблені з першокласною підтримкою для розділення єдиного графа модулів між сервером і клієнтом. Ось чому ми зараз співпрацюємо безпосередньо з розробниками пакувальників, щоб отримати вбудовані примітиви для RSC.

Завантаження ресурсів

Suspense дозволяє вказати, що відображати на екрані, поки дані або код для ваших компонентів ще завантажуються. Це дозволяє вашим користувачам поступово бачити більше вмісту під час завантаження сторінки, а також під час навігації маршрутизатором, яка завантажує більше даних і коду. Однак, з точки зору користувача, завантаження даних і рендеринг не дають повного уявлення про те, чи готовий новий контент. За замовчуванням браузери завантажують таблиці стилів, шрифти та зображення незалежно, що може призвести до стрибків інтерфейсу та послідовних змін макета.

Ми працюємо над повною інтеграцією Suspense з життєвим циклом завантаження таблиць стилів, шрифтів та зображень, щоб React враховував їх при визначенні готовності контенту до відображення. Без жодних змін у способі створення компонентів React, оновлення поводитимуться більш узгоджено та приємно. В якості оптимізації ми також надамо ручний спосіб попереднього завантаження ресурсів, таких як шрифти, безпосередньо з компонентів.

Наразі ми реалізуємо ці можливості і незабаром зможемо поділитися ще більше.

Метадані документа

Різні сторінки та екрани вашої програми можуть мати різні метадані, такі як тег <title>, опис та інші теги <meta>, специфічні для цього екрану. З точки зору супроводу, краще зберігати цю інформацію ближче до React-компонента для цієї сторінки або екрану. Однак HTML-теги для цих метаданих мають бути в документі <head>, який зазвичай рендериться в компоненті в самому корені вашого застосунку.

Сьогодні цю проблему вирішують одним із двох способів.

Однією з технік є рендеринг спеціального стороннього компонента, який переміщує <title>, <meta> та інші теги всередині нього у документ <head>. Це працює для основних браузерів, але є багато клієнтів, які не виконують JavaScript на стороні клієнта, наприклад, парсери Open Graph, і тому цей метод не є універсальним.

Інший метод полягає в тому, щоб розбити сторінку на дві частини. Спочатку виводиться основний вміст і збираються всі подібні теги. Потім <head> рендериться з цими тегами. Нарешті, <head> та основний вміст надсилаються браузеру. Цей підхід працює, але він не дозволяє вам скористатися перевагами Поткового рендерера сервера React 18, тому що вам доведеться чекати, поки весь контент буде відрендерено, перш ніж відправляти <head>.

Тому ми додаємо вбудовану підтримку рендерингу тегів <title>, <meta> та метаданих <link> будь-де у вашому дереві компонентів одарзу після установки. Це буде працювати однаково у всіх середовищах, включаючи повністю клієнтський код, SSR і в майбутньому RSC. Ми скоро розповімо про це докладніше.

Компілятор, що оптимізує React

З часу нашого попереднього оновлення ми активно працювали над дизайном React Forget, оптимізуючого компілятора для React. Раніше ми говорили про нього як про "компілятор з автоматичним запам'ятовуванням", і в деякому сенсі це правда. Але створення компілятора допомогло нам ще глибше зрозуміти модель програмування React. Кращий спосіб зрозуміти React Forget - це автоматичний реактивний компілятор.

Основна ідея React полягає в тому, що розробники визначають свій інтерфейс як функцію поточного стану. Ви працюєте зі звичайними значеннями JavaScript - числами, рядками, масивами, об'єктами - і використовуєте стандартні ідіоми JavaScript - if/else, for тощо - для опису логіки вашого компонента. Ментальна модель полягає в тому, що React буде повторно рендерити щоразу, коли стан застосунку змінюється. Ми вважаємо, що ця проста ментальна модель і близькість до семантики JavaScript є важливим принципом в моделі програмування React.

Проблема в тому, що React іноді може бути занадто реактивним: він може рендерити повторно занадто часто. Наприклад, у JavaScript немає простих способів порівняти, чи є два об'єкти або масиви еквівалентними (з однаковими ключами та значеннями), тому створення нового об'єкта або масиву при кожному рендерингу може призвести до того, що React буде виконувати більше роботи, ніж це дійсно потрібно. Це означає, що розробники повинні явно запам'ятовувати компоненти, щоб не реагувати на зміни надмірно.

Наша мета з React Forget полягає в тому, щоб React-застосунки за замовчуванням мали потрібний рівень реактивності: щоб застосунки повторно рендерилися лише тоді, коли значення стану значуще змінюються. З точки зору реалізації це означає автоматичне запам'ятовування, але ми вважаємо, що фреймворк реактивності є кращим способом зрозуміти React and Forget. Один із способів подумати про це полягає в тому, що React в даний час повторно рендерить, коли змінюється ідентичність об'єкта. З Forget, React перемальовує, коли змінюється семантичне значення - але без витрат часу виконання на глибокі порівняння.

Щодо конкретного прогресу, то з часу нашого останнього оновлення ми суттєво змінили дизайн компілятора, щоб узгодити його з підходом автоматичної реактивності та врахувати відгуки від внутрішнього використання компілятора. Після деяких значних рефакторів компілятора, починаючи з кінця минулого року, ми почали використовувати компілятор у виробництві в обмежених областях на Meta. Ми плануємо випустити його з відкритим вихідним кодом, як тільки доведемо його працездатність у виробництві.

Нарешті, багато людей висловили зацікавленість у тому, як працює компілятор. Ми з нетерпінням чекаємо можливості поділитися більш детальною інформацією, коли ми доведемо компілятор до розуму і викладемо його з відкритим кодом. Але вже зараз ми можемо поділитися деякими фрагментами:

Ядро компілятора майже повністю відокремлено від Babel, а API ядра компілятора є (приблизно) старим AST in, новим AST out (зі збереженням даних про розташування джерела). За лаштунками ми використовуємо власний конвеєр представлення та перетворення коду для низькорівневого семантичного аналізу. Однак, основний публічний інтерфейс до компілятора буде через Babel та інші плагіни системи збірки. Для зручності тестування наразі ми маємо плагін Babel, який є дуже тонкою обгорткою, що викликає компілятор для генерації нової версії кожної функції та її заміни.

Під час рефакторингу компілятора за останні кілька місяців ми хотіли зосередитися на вдосконаленні основної моделі компіляції, щоб переконатися, що зможемо впоратися з такими складнощами, як умовні умови, цикли, перепризначення та мутації. Однак JavaScript має багато способів виразити кожну з цих функцій: if/else, тернарні конструкції, for, for-in, for-of тощо. Намагання підтримати всю мову одразу відтермінувало б момент, коли ми змогли б перевірити основну модель. Замість цього ми почали з невеликої, але репрезентативної підмножини мови: let/const, if/else, цикли for, об'єкти, масиви, примітиви, виклики функцій та деякі інші функції. По мірі того, як ми набували впевненості в основній моделі та вдосконалювали наші внутрішні абстракції, ми розширювали підмножину підтримуваних мов. Ми також чітко вказуємо на синтаксис, який ми ще не підтримуємо, ведемо діагностику і пропускаємо компіляцію для непідтримуваних вхідних даних. У нас є утиліти для тестування компілятора на кодових базах Meta і визначення найпоширеніших непідтримуваних можливостей, щоб ми могли визначити пріоритети для них у подальшому. Ми продовжимо поступово розширюватися у напрямку підтримки всієї мови.

Щоб зробити звичайний JavaScript у React-компонентах реактивним, потрібен компілятор з глибоким розумінням семантики, щоб точно розуміти, що робить код. Використовуючи цей підхід, ми створюємо систему реактивності в JavaScript, яка дозволяє вам писати код продукту будь-якої складності з повною виразністю мови, замість того, щоб обмежуватися мовою, специфічною для певної галузі.

Екранний рендеринг

Позаекранний рендеринг - це майбутня можливість у React для рендерингу екранів у фоновому режимі без додаткових витрат на продуктивність. Ви можете думати про це як про версію CSS-властивості content-visibility , яка працює не тільки для DOM-елементів, але й для React-компонентів. Під час нашого дослідження ми виявили різноманітні варіанти використання:

  • Маршрутизатор може показувати екрани у фоновому режимі, щоб коли користувач перейде до них, вони були миттєво доступні.
  • Компонент перемикання вкладок може зберігати стан прихованих вкладок, щоб користувач міг перемикатися між ними без втрати прогресу
  • Компонент віртуалізованого списку може попередньо відрендерити додаткові рядки вище та нижче видимого вікна.
  • При відкритті модального або спливаючого вікна решту програми можна перевести у "фоновий" режим, щоб події та оновлення були вимкнені для всього, крім модального вікна.

Більшість React-розробників не взаємодіятимуть із позаекранними API React безпосередньо. Натомість, позаекранний рендеринг буде інтегровано в такі речі, як маршрутизатори та бібліотеки інтерфейсу користувача. І тоді розробники, які використовують ці бібліотеки, автоматично отримають переваги без додаткової роботи.

Ідея полягає в тому, що ви повинні мати можливість виводити будь-яке дерево React за межі екрану, не змінюючи спосіб написання ваших компонентів. Коли компонент рендериться за межами екрану, він фактично не монтується до тих пір, поки компонент не стане видимим - його ефекти не спрацьовують. Наприклад, якщо компонент використовує useEffect для запису аналітики, коли він з'являється вперше, попередній перегляд не вплине на точність цієї аналітики. Аналогічно, коли компонент виводиться за межі екрана, його ефекти також відмонтовуються. Ключовою особливістю позаекранного рендерингу є те, що ви можете перемикати видимість компонента без втрати його стану.

З часу нашого останнього оновлення ми протестували експериментальну версію внутрішнього пререндерингу в Meta в наших React Native додатках на Android та iOS, і отримали позитивні результати. Ми також покращили роботу позаекранного рендерингу з Suspense - призупинення всередині позаекранного дерева не спричинятиме відключення Suspense. Робота, яку нам залишилося зробити, полягає у доопрацюванні примітивів, доступних для розробників бібліотек. Ми плануємо опублікувати RFC пізніше цього року, а також експериментальний API для тестування та отримання відгуків.

Трасування переходів

API трасування переходів дозволяє виявити, коли React-переходи стають повільнішими, і дослідити причини цього. Після нашого останнього оновлення ми завершили початковий дизайн API і опублікували RFC. Також було реалізовано базові можливості. Наразі проект призупинено. Ми чекаємо на відгуки про RFC і з нетерпінням чекаємо на відновлення його розробки, щоб забезпечити кращий інструмент вимірювання продуктивності для React. Це буде особливо корисно для маршрутизаторів, побудованих на основі React Transitions, таких як Next.js App Router.


На додаток до цього оновлення, наша команда нещодавно виступила в якості гостя на подкастах і прямих ефірах спільноти, щоб розповісти більше про нашу роботу і відповісти на запитання.

Завдяки Andrew Clark, Dan Abramov, Дейв Маккейб, Луна Вей, Метт Керролл, Шону Кігану, Себастьяну Зільберманну, Сету Вебстеру та Софі Альперт за рецензування цієї статті.

Дякуємо за увагу, до наступного оновлення!