Skip to content

Instantly share code, notes, and snippets.

@monochromer
Last active August 1, 2023 05:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save monochromer/d5fff85e6090c0402b38f70599bb1310 to your computer and use it in GitHub Desktop.
Save monochromer/d5fff85e6090c0402b38f70599bb1310 to your computer and use it in GitHub Desktop.
React Lazy Hydration

https://t.me/super_oleg_dev/102

Ленивая гидрация позволяет не выполнять реакту код компонента на клиенте сразу при гидрации всего приложения, и отлично комбинируется с IntersectionObserver - можно выполнять код только при попадании компонента в область видимости.

Также, можно вообще не выполнять код на клиенте пометив блок как статичный, и каким-либо образом убирать его из клиентского бандла.

Механизм достаточно простой, это по сути легализованный хак в React, детали есть в этом issue - facebook/react#10923 (comment)

Код для lazy обертки может базово выглядеть так:

const LazyRender = ({ children }) => {
  const containerRef = useRef(null);
  const isVisible = useObserver(containerRef);

  if (isVisible) {
    return React.createElement('div', {}, children);
  }

  return React.createElement('div', {
    ref: containerRef,
    suppressHydrationWarning: true,
    dangerouslySetInnerHTML: { __html: '' },
  });
};

Абстрактный хук useObserver должен вернуть true всегда на сервере, либо при попадании компонента в область видимости на клиенте - то есть мы сознательно создаем ошибку гидрации, и гасим ее через пропс suppressHydrationWarning: true.

Вся магия, почему разметка на клиенте не ломается, а переиспользуется, как раз заключается в хаке с dangerouslySetInnerHTML: { __html: '' }.

У решения конечно же есть минусы:

  • приходится создавать лишний тег - враппер
  • по умолчанию код все-равно остается в клиентском бандле
const LazyRender = ({ children, cacheEnabled, cacheKey, serverCache }) => {
  const containerRef = useRef(null);
  const isVisible = useObserver(containerRef);

  if (cacheEnabled && typeof window === 'undefined') {
    let html: string;

    if (serverCache.has(cacheKey)) {
      html = serverCache.get(cacheKey);
    } else {
      const reactDomServer = require('react-dom/server');

      html = reactDomServer.renderToString(children);

      serverCache.set(cacheKey, html);
    }

    return React.createElement('div', {
      dangerouslySetInnerHTML: { __html: html },
    });
  }

  if (isVisible) {
    return React.createElement('div', {}, children);
  }

  return React.createElement('div', {
    ref: containerRef,
    suppressHydrationWarning: true,    dangerouslySetInnerHTML: { __html: '' },
  });
};

Основной минус подхода - React Context приложения будет недоступен в children компоненте. Мы планируем использовать это для микрофронта (а футер как раз такой микрофронт), где это кажется не проблема, и даже хорошая практика.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment