기본 테마의 플래시 방지: React 앱의 다크 모드 구현

어두운 모드로 전환하기 전에 페이지를 방문하고 밝은 모드를 경험한 적이 있습니까? 최근에 저는 다크 모드를 지원해야 하는 프로젝트를 진행했습니다(지금 쿨한 아이들이 하는 일이기 때문에). 약간의 연구 끝에 기본 테마의 원하지 않는 플래시를 방지할 수 있는 구현을 발견했으며 귀하와 미래의 자신을 위해 기록하고 싶습니다.

This article is written for SSR/SSG React apps such as Next.js, GatsbyJS, but the same logic can be applied to single-page apps like CRA or other frameworks like Vuejs, Angular, etc.



왜 플래시인가?



라이트/다크 모드를 구현할 때 종종 localStorage 또는 prefers-color-scheme와 같은 클라이언트 측 전용 기능에 도달하여 useEffect 후크에 넣어야 합니다. 이는 미리 렌더링된 HTML 및 스타일에 기본 테마가 필요함을 의미합니다. 방문자가 페이지를 열면 후크에서 예약된 업데이트가 실행되기 전에 HTML 요소가 기본 스타일로 구문 분석되고 렌더링되어 플래시가 실행됩니다.

Here is an example from usehooks.com/useDarkMode.



더 나은 구현



이러한 플래시를 방지하기 위해 React 세계에서 테마를 관리하는 논리를 추출하고 HTML 요소가 구문 분석되고 렌더링되기 전에 실행되도록 HTML 요소 위에 배치된 별도의 스크립트로 이동할 수 있습니다.

<!DOCTYPE html>
<html>
  <head>
    <title>Create Next App</title>
    <!-- links to stylesheets -->
  </head>
  <body>
    <script>
      // 🌟 logic for managing themes goes here 
    </script>
    <div id="__next">
      <!-- content -->
    </div>
    <script src="/bundled.js"></script>
  </body>
</html>

To add the script, you will have to edit _document.js in Next.js projects or html.js in GatsbyJS projects.



스크립트는 다음 작업을 수행합니다.
  • 전역__onThemeChange 변수를 React 구성 요소에 의해 덮어쓰게 될 no-op 함수로 초기화합니다.
  • 은 호출 시 본문__setPreferredTheme을 업데이트하고 선택한 테마를 className에 저장하는 전역localStorage; 함수를 선언합니다.
  • 은 테마를 localStorage에 저장된 테마로 초기화하고 시스템 테마로 대체합니다.

  • // wrapped as IIFE to use private variables and functions
    (function () {
      function setTheme(newTheme) {
        document.body.className = newTheme; // "dark" or "light"
        window.__theme = newTheme;
        window.__onThemeChange(newTheme);
      }
      // this will be overwritten in our React component
      window.__onThemeChange = function () {};
      // this will be triggered by our React component
      window.__setPreferredTheme = function (newTheme) {
        setTheme(newTheme);
        try {
          localStorage.setItem("theme", JSON.stringify(window.__theme));
        } catch (err) {}
      };
      // detect system theme and monitor for changes
      const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
      darkQuery.addListener(function (event) {
        window.__setPreferredTheme(event.matches ? "dark" : "light");
      });
      let preferredTheme;
      // try to get saved theme
      try {
        preferredTheme = JSON.parse(localStorage.getItem("theme"));
      } catch (err) {}  
      // initialize preferredTheme
      setTheme(preferredTheme || (darkQuery.matches ? "dark" : "light"));
    })();
    

    Please note the script is render-blocking so you should keep it short. Also, it will not be compiled by Babel so you should avoid using new JS features.



    전역 스타일시트에서 CSS className을 기반으로 CSS 변수를 업데이트할 수 있습니다.

    body {
      --background: #faf4f8;
      --text-color: rgba(0, 0, 0, 0.87);
      --link: #3182ce;
    }
    body.dark {
      --background: #1a202c;
      --text-color: #f7fafc;
      --link: #90cdf4;
    }
    

    이제 자신만의 ThemeProvideruseTheme 후크를 만들어 생성된 전역 함수를 연결할 수 있습니다.

    import React, { useState, useEffect, useContext } from "react";
    
    const ThemeContext = React.createContext("light");
    
    export function ThemeProvider({ children }) {
      const [theme, setTheme] = useState(global.window?.__theme || "light");
      const toggleTheme = () => {
        global.window.__setPreferredTheme(theme === "light" ? "dark" : "light");
      };
      useEffect(() => {
        global.window.__onThemeChange = setTheme;
      }, []);
      return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
          {children}
        </ThemeContext.Provider>
      );
    }
    
    export const useTheme = () => useContext(ThemeContext);
    

    다음은 Next.js를 사용한 데모입니다.



    GatsbyJS에서 구현하려면 source codeDan Abramov's blog — Overreacted.io을 확인하십시오. 나는 그것으로부터이 접근 방식을 배웠습니다.

    나중에 생각



    React 앱을 개발할 때 우리는 React로 모든 작업을 수행하고 모든 로직을 React 구성 요소에 넣는 경향이 있습니다. 테마를 구현한 경험은 React 세계 외부에서 코드를 작성하고 나중에 React에 바인딩하는 것이 괜찮다는 것을 상기시켜 줍니다. 결국 React는 사용자 인터페이스를 구축하기 위한 라이브러리일 뿐입니다. 이 경우 원활한 사용자 경험을 생성하려면 브라우저 렌더링이 작동하는 방식을 이해하는 것이 필수적입니다.

    읽어 주셔서 감사합니다. 차오!



    React Summit is coming back on October 15-16. There'll be speakers like Kent C. Dodds, Max Stoiber, Sara Vieira, Sharif Shameem, and more.

    Register for free before September 20: https://ti.to/gitnation/react-summit?source=REFURCR-1.

    좋은 웹페이지 즐겨찾기