개발공부/웹

React에서 Context로 다크모드 구현하기

개발자 찐빵이 2023. 5. 7. 13:20
728x90

몇 달 전, 바닐라JS에서 CSS로 다크모드 설정하는 방법을 공부했다.

그리고 리액트를 공부하다가 다시 한번 다크모드 설정을 구현하게 되었는데,
너모너모 쉬워서 깜짝 놀랐다.

잊고 싶지 않아서 글로 남겨본다.

1. 다크모드 Context 만들기

앱 전반적으로 활용해야하는 설정이라면 Context를 사용해서 구현하는 게 편리하다.
다크모드도 앱 전체에 적용되어야 하는 값이기 때문에 Context를 활용하여 구현해 준다.

// DarkModeContext.jsx

import { createContext, useContext, useEffect, useState } from 'react';

export const DarkModeContext = createContext();

export function DarkModeProvider({ children }) {
  const [darkMode, setDarkMode] = useState(false);

  // 토글 버튼을 활용해 다크모드를 변경할 수 있게 구현했다.
  const toggleDarkMode = () => {
    setDarkMode(!darkMode);
    updateDarkMode(!darkMode);
  };

// 다크모드 초기화
  useEffect(() => {
      // 로컬 스토리지에 다크모드 값이 있다면 그 값을 사용하고, 없는 경우 윈도우 설정을 따라간다.
    const isDark =
      localStorage.theme === 'dark' ||
      (!('theme' in localStorage) &&
        window.matchMedia('(prefers-color-scheme: dark)').matches);

    setDarkMode(isDark);
    updateDarkMode(isDark);
  }, []);

  return (
    <DarkModeContext.Provider value={{ darkMode, toggleDarkMode }}>
      {children}
    </DarkModeContext.Provider>
  );
}

// 다크모드 변경 함수
function updateDarkMode(darkMode) {
  if (darkMode) {
    document.documentElement.classList.add('dark'); // html 클래스에 'dark' 값을 추가한다.
    localStorage.theme = 'dark';
  } else {
    document.documentElement.classList.remove('dark'); // html 클래스에 'dark' 값을 제거한다.
    localStorage.theme = 'light';
  }
}

// 내부적으로 useContext를 사용해서 다크모드 Context를 만들어주고, 외부에서는 신경쓰지 않도록 해준다.
export const useDarkMode = () => useContext(DarkModeContext);

2. 다크모드 Context 적용하기

구현한 Context를 앱에 적용해 준다.
사용법은 간단하다. DarkModeProvider로 감싸주면 된다.
이제 모든 자식 노드에서 다크모드 값에 접근할 수 있다.

//app.js
...
function App() {
  const [filter, setFilter] = useState(filters[0]);

  return (
    <DarkModeProvider> // 여기!
      <Header filters={filters} filter={filter} onFilterChange={setFilter} />
      <TodoList filter={filter} />
    </DarkModeProvider>
  );
}
...

3. 다크모드 토글링 연결

다크모드를 토글링할 버튼에 다크모드 토글링 함수를 연결해 준다.

// Header.jsx
...
export default function Header({ filters, filter, onFilterChange }) {
  const { darkMode, toggleDarkMode } = useDarkMode();

  return (
    <header className={styles.header}>
      <button className={styles.darkModeButton} onClick={toggleDarkMode}> // 클릭 시 다크모드를 토글한다.
        {darkMode ? <BsSunFill /> : <BsMoonFill />} // 다크모드인지 여부에 따라 아이콘을 다르게 보여준다.
      </button>
    </header>
  );
}

4. 다크모드 스타일 연결

다크모드가 true인 경우 html 클래스를 추가해서 다크모드를 설정할 수 있도록 구현했다.
이 클래스 값을 사용해서 style에 정의한 값을 변경해 준다.

// index.css

// 다크모드가 아닌 경우 사용하는 색상 값
:root{
  --color-bg-dark: #f5f5f5;
  --color-bg: #fdfffd;
  --color-gray: #d1d1d1;
  --color-text: #22243b;
  --color-accent: #f16e03;
  --color-white: white;
  --color-scrollbar: #aaa7a7;
}

// 다크모드인 경우 사용하는 색상 값
html.dark {
  --color-bg-dark: #1a1c35;
  --color-bg: #22243b;
  --color-gray: #707070;
  --color-text: #f5f5f5;
}
반응형