Как стать автором
Обновить
31.27
Outlines Tech
Технологический партнер для крупного бизнеса

Как улучшить UX в PWA на React с помощью потокового Backend-Driven UI — личный опыт

Уровень сложностиСредний
Время на прочтение6 мин
Количество просмотров1.3K

Привет! Меня зовут Ярослав, я фронтенд-разработчик в Outlines Tech. В одном из PWA-проектов с Backend-Driven UI (BDUI) я столкнулся с проблемой: интерфейс загружался слишком медленно. Пользователи видели спиннер и ждали более 15 секунд, пока страница заработает: интерфейс не начинал функционировать, пока не приходили все данные. За это время большинство пользователей теряли терпение и просто закрывали вкладку.

Медленная загрузка как конечный результат — меня не устроил. Это бесило, ведь при загрузке интерфейс мог бы работать хотя бы частично. Я начал искать решение проблемы, потому что это был вызов — как сделать UX удобнее и быстрее. В процессе я использовал собственный опыт, иногда ИИ подсказал возможные решения, а также нашёл open-source библиотеки и использовал хаки, которые ускоряли работу интерфейса.

Ниже хочу показать три приёма, как можно ускорить загрузку интерфейсов с Backend-Driven UI и улучшить UX. Решения показали хорошие результаты на демо-версии, но увы, пока ещё не внедрены в реальный проект. Было бы интересно обсудить с вами, как эти приёмы могут помочь в боевых задачах и что ещё можно улучшить.

Почему тормозит интерфейс при Backend-Driven UI

Немного вводных. Backend-Driven UI (BDUI) — это подход, при котором интерфейс генерируется на сервере и передаётся клиенту в виде данных, чаще всего в формате JSON. Пользователь получает описание и собирает на его основе страницу: какие компоненты отобразить, в каком порядке и с каким состоянием. Такой подход используют в крупных компаниях, таких как Яндекс, Альфа-Банк, Циан, Ozon.

Слабое место — синхронный парсинг JSON

Сам подход BDUI не вызывает проблем с производительностью. Проблема возникает из-за того, как браузер обрабатывает JSON на клиенте. Когда данные передаются и парсятся синхронно, это блокирует главный поток. Синхронный парсинг JSON заставляет интерфейс ждать завершения обработки всех данных перед тем, как он начнёт рендериться, что замедляет взаимодействие с пользователем.

Когда JSON-файл слишком большой, например, 80-100 КБ, браузер не может параллельно загружать и обрабатывать данные. Вместо этого весь файл должен быть распарсен синхронно, прежде чем интерфейс начнёт рендериться. Пока парсинг не завершится, интерфейс остаётся неактивным — пользователь видит только спиннер, даже если часть данных уже пришла.

Как синхронный парсинг JSON влияет на производительность и UX

  1. Задержка в рендеринге. Из-за синхронного парсинга браузер не может параллельно выполнять другие задачи

  2. Проблемы с отзывчивостью. Когда данные поступают поэтапно, а интерфейс состоит из множества динамических компонентов, синхронный парсинг усиливает задержки

  3. Медленная загрузка. Из-за того, что браузер синхронно парсить JSON, весь процесс загрузки данных и рендеринга замедляется

В PWA на вебе нельзя заранее загрузить все возможные виджеты, как это делают в мобильных приложениях. На смартфоне пользователь один раз скачивает всё из магазина, а в браузере так не получится. Если подтягивать все компоненты сразу, страница будет дольше открываться и человек может просто закрыть вкладку. Поэтому JavaScript-файлы загружаются только при необходимости — когда из JSON становится понятно, какие блоки пришли с бэка.

Чтобы избавиться от этих проблем, я решил применить потоковую загрузку JSON через fetch, частичный парсинг и постепенный рендеринг компонентов. Ниже показываю как

Три приёма, чтобы решить проблему с долгой загрузкой

Чтобы ускорить рендеринг интерфейса, стоит изменить подход к обработке данных. Вместо того чтобы ждать полной загрузки всего JSON, можно загружать его по частям и сразу показывать интерфейс. Это позволяет постепенно загружать страницу, избавляет от спиннера и улучшает восприятие интерфейса.

Ниже — три приёма, которые я тестировал на демо-версии и результаты мне понравились:

Приём №1. Потоковая загрузка JSON через fetch

Я использовал ReadableStream — браузерное API, которое позволяет читать данные порциями. Как только приходит первая часть, можно сразу начать её обрабатывать, что сокращает задержку между отправкой запроса и появлением первых элементов интерфейса.

const sendRequest = async (fetcher: () => Promise<Response>) => {
  const res = await fetcher();

  if (!res.ok) throw new Error(await res.text());

  const reader = res.body?.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;

      const chunk = decoder.decode(value, { stream: true });

      // Обработка порции данных
      parser.write(chunk);
    }
  } finally {
    reader?.releaseLock();
  }
};

Такой подход позволяет начать обработку данных ещё до их полной загрузки

Приём №2. Частичный парсинг

Если JSON-файл слишком большой, парсинг через JSON.parse блокирует главный поток и тормозит интерфейс. Вместо этого я использовал библиотеку incomplete-json-parser, которая разбирает данные по мере поступления.

import { IncompleteJsonParser } from "incomplete-json-parser";

const parser = new IncompleteJsonParser();
parser.write(chunk); // отправка очередной порции в парсер

Такой парсер не дожидается полной последовательности и продолжает работу, пока приходят новые данные

Библиотека incomplete-json-parser работает иначе, чем стандартный JSON.parse. Она не требует, чтобы JSON-документ был получен полностью, включая завершающие скобки. Если данные поступают частями, JSON.parse завершит работу с ошибкой, тогда как incomplete-json-parser продолжает обрабатывать уже поступившую информацию. Такой подход позволяет начать разбор раньше, уменьшить задержки и ускорить отображение интерфейса.

Да, такой парсер работает медленнее встроенного — он написан на JavaScript, а не на C++. Но он даёт главное — интерфейс остаётся отзывчивым, даже если JSON большой или приходит медленно. По моему мнению, это особенно важно на слабых устройствах и при нестабильной сети.

Приём №3. Постепенный рендеринг компонентов

Обычно в классическом BDUI-flow после получения JSON разработчик строит новое виртуальное DOM-дерево и отдаёт его React для рендеринга. Но если JSON большой, это приводит к резкому росту потребления памяти. Сборщик мусора начинает срабатывать чаще, а интерфейс начинает тормозить.

Чтобы сократить потребление памяти, я решил не формировать новое виртуальное дерево. Вместо этого я брал уже полученный JSON и использовал его напрямую как основу для виртуального DOM. 

useEvent(
  "data",
  (event) => {
    const { detail } = event as CustomEvent;
    const items: ReactElement[] = detail?.templates?.landing?.items || [];

    for (const item of items) {
      if ('$$type' in item) continue;

      Object.defineProperties(item, {
        $$typeof: { value: Symbol.for("react.transitional.element"), configurable: true },
        type: { value: SignalValue, configurable: true },
        props: {
          configurable: true,
          get() { return { data: this }; },
        },
        ref: { value: null, configurable: true },
      });
    }

    setData(items);
  },
  stream.eventTarget,
  { signal: ac.current.signal }
);

function SignalValue({ data }) {
  return JSON.stringify(data);
}

Для React 18 и ниже вместо react.transitional.element следует использовать react.element

Постепенный рендеринг помогает обновлять компоненты без создания нового дерева виртуального DOM. Это снижает использование памяти и устраняет фризы, связанные с работой сборщика мусора. Рендеринг выполняется на уже существующих данных, что делает интерфейс более отзывчивым и интерактивным.

Когда и как использовать эти приёмы

Все три приёма можно использовать как вместе, так и по отдельности, в зависимости от сложности задачи. Вы можете выбрать подход, который решает именно вашу проблему, или комбинировать их для максимального эффекта. 

Например, если проект небольшой и данные на страницах не слишком велики, можно начать с одного из подходов — например, с потоковой загрузки. Если проект сложный, с большим объёмом данных и динамическим интерфейсом, лучше использовать все три приёма в комплексе.

Что у меня изменилось после внедрения

После перехода на потоковую загрузку, частичный парсинг и постепенный рендеринг интерфейс стал работать быстрее. Несмотря на то, что решения ещё не внедрены в реальный проект, результаты на демо-версии показали явное улучшение:

  • Потоковая загрузка сократила время ожидания. Данные начали загружаться и обрабатываться по частям за счёт чего первые элементы интерфейса отображаются сразу, а не только после загрузки всего JSON.

  • Частичный парсинг улучшил производительность. Чтобы не ждать полного парсинга всего JSON-файла, данные теперь обрабатываются по мере поступления. Это позволяет не блокировать главный поток и не замедлять интерфейс. 

  • Постепенный рендеринг загружает контент частями и избавляет от спиннера. Раньше пользователь видел только спиннер в течение нескольких секунд, а теперь страница начинает собираться постепенно: шапка, первые блоки и всё остальное.

В итоге интерфейс раньше становится интерактивным. Можно кликать по ссылкам, скроллить и взаимодействовать с элементами ещё до полной загрузки страницы.

Краткая выжимка статьи

Как ускорить интерфейс с Backend-Driven UI с помощью трёх приёмов:

  1. Потоковая загрузка JSON через fetch — уменьшает время ожидания загрузки

  2. Частичный парсинг — не блокирует поток и ускоряет обработку

  3. Постепенный рендеринг — снижает нагрузку на память

Эти приёмы решают несколько проблем:

  1. Сокращается время загрузки:пользователь сразу видит первую часть интерфейса, не ожидая загрузки всего JSON

  2. Ускоряется интерактивность с интерфейсом: даже если данные не пришли полностью, пользователь уже может взаимодействовать с тем, что загружено

  3. Повышается отзывчивость интерфейса: страницы больше не «зависают» из-за большого JSON, что заметно на слабых устройствах и при плохом интернете

Заглядывайте в Telegram-канал Outlines Tech. Там мы с командой делимся кейсами на тему IT, обсуждаем работу, рассказываем про карьерный рост и публикуем мемы.

Буду рад услышать мнение и обсудить, какие приёмы, по вашему опыту, сработают лучше всего. Расскажите, как вы решали подобные задачи на своём проекте?

Теги:
Хабы:
+2
Комментарии10

Публикации

Информация

Сайт
outlines.tech
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия