Как отразить состояние хука в разных островах 🏝

«Островная» архитектура — это термин, введенный относительно недавно, который описывает фронтенды, состоящие из нескольких точек входа. Она бросает вызов традиционному подходу к визуализации гигантского дерева компонентов, позволяя более четко изолировать динамические, гидратируемые элементы от статического контента. И это заложено в Fresh, новый фреймворк для Deno, который я сейчас использую для проекта (скоро будет)!

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

Хук, о котором пойдет речь ☀️/🌙

Чтобы включить предпочтение темного режима в моем проекте, я добавил этот простой хук для взаимодействия с "prefers-color-scheme: dark", добавив класс dark в элемент body и установив его в localstorage, чтобы сохранить любое переопределение предпочтения:

export function useDarkMode() {
    const [dark, setDark] = useState(false);

    function toggleDarkMode() {
      const prefersDark = document.body.classList.toggle('dark');
      setDark(prefersDark);
      localStorage.setItem('prefers-dark', prefersDark);
    }

    useEffect(() => {
      const prefersDark = localStorage.getItem('prefers-dark') === 'true';
      const devicePrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

      if ((prefersDark === null || prefersDark) && devicePrefersDark) {
        toggleDarkMode();
      }
    }, []);

    return [dark, toggleDarkMode];
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Это работает для дерева рендеринга, в котором есть кнопка, запускающая toggleDarkMode, но из-за островного подхода это дерево рендеринга (и состояние внутри него) полностью изолировано от других. Чтобы гарантировать, что все элементы находятся в правильном состоянии dark независимо от точки входа, необходимо иметь спасательный круг между островами.

Введите: диспетчеризация событий 🛟.

Хотя существует множество подходов к решению этой проблемы (MutationObserver и т.д.), самым простым является отправка события, которое могут прослушать другие экземпляры этого хука.

В данном случае это заставляет каждый остров вызывать функцию toggleDarkMode и (при соответствующих условиях) синхронизировать свое состояние с состоянием сработавшего экземпляра хука. Вот изменения, необходимые для вышеупомянутого хука, чтобы добиться этого:

export function useDarkMode() {
    function toggleDarkMode() {
        // same code as above
        window.dispatchEvent(new Event('dark-mode-preference-updated'));
    }

    function respondToEvent() {
        const prefersDark = document.body.classList.contains('dark');
        setDark(prefersDark);
    }

    useEffect(() => {
        const prefersDark = localStorage.getItem('prefers-dark') === 'true';
        const devicePrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

        if ((prefersDark === null || prefersDark) && devicePrefersDark) {
            if (!document.body.classList.contains('dark')) {
                toggleDarkMode();
            } else if (!dark) {
                setDark(true);
            }
        }

        window.addEventListener('dark-mode-preference-updated', respondToEvent);

        return () => {
            window.removeEventListener('dark-mode-preference-updated');
        };
    }, []);
}
Войти в полноэкранный режим Выход из полноэкранного режима

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

Затем, любой вызов toggleDarkMode вызовет событие для каждого другого экземпляра хука, которое заставит каждый из них проверить значение на body и сохранить его в состоянии без выполнения каких-либо мутаций.

Значение localstorage устанавливается только срабатывающим хуком, поэтому последующие загрузки страниц будут получать правильное значение предпочтений.


Хотя это может противоречить некоторым принципам, которые хуки призваны упростить в отношении общего состояния, это позволяет логике, разделяемой между компонентами, жить в одном месте. Реализация этого хука упрощена тем, что document.body.classList является источником истины, но для синхронизации данных между экземплярами можно использовать более сложные события. В любом случае, дайте мне знать, что вы думаете, & если у вас есть другие предложения по зеркалированию состояния между различными точками входа!

Оцените статью
devanswers.ru
Добавить комментарий