Pull to refresh

React Islands: React-виджеты на чужой HTML-странице

Иногда React нужен не на всю страницу, а точечно: баннер, форма, счётчик. Host при этом — CMS, легаси или другой фреймворк. Поднимать SPA ради пары виджетов избыточно.

Архитектура islands: страница остаётся обычным HTML, а интерактив монтируется отдельными «островами». Библиотека react-islands фиксирует простой контракт: разметка → регистрация → invoke().

Контракт

Host Библиотека data-react-island="<name>" invoke() находит узел и монтирует renderer data-react-island-<attr> уходит в props, если указан в attributes — при mount ставится data-react-island-inited

<div
  data-react-island="banner"
  data-react-island-title="Акция"
  data-react-island-href="/promo"
>
  Fallback до JS
</div>

Использование

import { useEffect } from 'react';
import { Runner, setComponent, getWrapper } from 'react-islands';
import type { ComponentInvokeProps } from 'react-islands';

function Banner({ title, href, innerHTML, onMount }: ComponentInvokeProps & {
  title?: string;
  href?: string;
}) {
  useEffect(() => { onMount?.(); }, [onMount]);

  return (
    <a href={href}>
      <strong>{title}</strong>
      {innerHTML && <span dangerouslySetInnerHTML={{ __html: innerHTML }} />}
    </a>
  );
}

setComponent('banner', (element) => {
  getWrapper({ element, component: Banner, attributes: ['title', 'href'] });
});

Runner.getInstance().invoke();

Повторный invoke() безопасен: already inited узлы пропускаются.

Динамика

const el = document.createElement('div');
el.setAttribute('data-react-island', 'banner');
el.setAttribute('data-react-island-title', 'Ещё баннер');
container.append(el);

runner.render(el);   // смонтировать один
runner.unmount(el);  // снять root перед удалением из DOM
el.remove();

Когда брать

Да: виджеты на CMS/легаси, props из разметки, отдельный bundle островов.
Нет: уже есть React SPA или нужна общая store-шина между виджетами.

Код и демо: gitflic.ru/project/yamakca/react-islands.

Tags:
+3
Comments2

Articles