리액트 다크모드 알아보기(feat. MUI)

성태팍·2024년 11월 26일
post-thumbnail

다크 모드는 문자 그대로 화면 디자인을 어둡게 만들어서 사용자 경험을 개선하고 배터리 소모를 줄이는 데 사용하는 기능이다. 리액트의 상태 관리를 이용하면 구현 가능하고 MUI(Material-UI)와 같은 UI 프레임워크는 강력한 테마 관리 시스템을 통해 손쉽게 구현 가능하다. 각각 다크모드를 구현하는 방법과 동작하는 방식을 간단하게 알아보자.

1. React에서 다크 모드 구현

React에서 다크 모드를 구현하려면 상태를 기반으로 다크 모드와 라이트 모드를 동적으로 전환하면 된다.

1.1 기본적인 다크 모드 구현

1) 상태 관리로 다크 모드 토글

import React, { useState } from "react";
import "./App.css"; // 다크 모드 스타일 정의

function App() {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const toggleDarkMode = () => setIsDarkMode(!isDarkMode);

  return (
    <div className={isDarkMode ? "dark-mode" : "light-mode"}>
      <button onClick={toggleDarkMode}>
        {isDarkMode ? "Light Mode" : "Dark Mode"}
      </button>
      <p>현재 모드: {isDarkMode ? "다크 모드" : "라이트 모드"}</p>
    </div>
  );
}

export default App;

2) CSS로 스타일 적용

body {
  margin: 0;
  padding: 0;
  font-family: Arial, sans-serif;
}

.light-mode {
  background-color: #ffffff;
  color: #000000;
}

.dark-mode {
  background-color: #121212;
  color: #ffffff;
}

1.2 로컬 스토리지로 사용자 설정 유지

다크 모드 상태를 브라우저의 localStorage에 저장하여 새로고침 시에도 사용자 설정을 유지할 수 있다.

import React, { useState, useEffect } from "react";

function App() {
  const [isDarkMode, setIsDarkMode] = useState(() => {
    // 초기값을 localStorage에서 가져옴
    return localStorage.getItem("darkMode") === "true";
  });

  const toggleDarkMode = () => {
    const newMode = !isDarkMode;
    setIsDarkMode(newMode);
    localStorage.setItem("darkMode", newMode); // localStorage에 상태 저장
  };

  useEffect(() => {
    document.body.className = isDarkMode ? "dark-mode" : "light-mode";
  }, [isDarkMode]);

  return (
    <button onClick={toggleDarkMode}>
      {isDarkMode ? "Light Mode" : "Dark Mode"}
    </button>
  );
}

export default App;

1.3 시스템 설정 감지

사용자의 시스템 설정(다크 모드 여부)을 감지하려면 window.matchMedia를 사용할 수 있다.

import React, { useEffect, useState } from "react";

function App() {
  const [isDarkMode, setIsDarkMode] = useState(
    window.matchMedia("(prefers-color-scheme: dark)").matches
  );

  useEffect(() => {
    const listener = (e) => setIsDarkMode(e.matches);
    const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
    darkModeQuery.addEventListener("change", listener);
    return () => darkModeQuery.removeEventListener("change", listener);
  }, []);

  return (
    <div className={isDarkMode ? "dark-mode" : "light-mode"}>
      <p>현재 모드: {isDarkMode ? "다크 모드" : "라이트 모드"}</p>
    </div>
  );
}

export default App;

window.matchMedia는 브라우저 자체에서 제공하는 메서드로 미디어쿼리 문자열을 분석하여 그 결과를 반환한다. 사용자 시스템 테마를 확인하기 위해서는 prefers-color-scheme 미디어 쿼리를 사용해야 확인 가능하다.

2. MUI에서 다크 모드 구현

Material-UI(MUI)는 React의 UI 라이브러리로 테마 시스템을 통해 다크 모드를 쉽게 지원한다.

2.1 MUI 다크 모드 설정

MUI의 createTheme와 ThemeProvider를 사용

import React, { useState } from "react";
import { createTheme, ThemeProvider, CssBaseline } from "@mui/material";
import Button from "@mui/material/Button";

function App() {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const theme = createTheme({
    palette: {
      mode: isDarkMode ? "dark" : "light",
    },
  });

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Button
        variant="contained"
        onClick={() => setIsDarkMode(!isDarkMode)}
      >
        {isDarkMode ? "Light Mode" : "Dark Mode"}
      </Button>
    </ThemeProvider>
  );
}

export default App;

2.2 MUI 테마의 동작 원리

1. createTheme로 테마 생성

  • 다크 모드와 라이트 모드를 포함한 palette 속성 정의
  • mui 컴포넌트는 테마를 자동으로 읽어들여 다크 모드와 라이트 모드에 맞게 스타일 조정

2. ThemeProvider로 테마 제공

  • React의 Context API를 활용하여 애플리케이션 전역에서 테마를 사용

3. CssBaseline으로 기본 스타일 초기화

  • 다크 모드에 맞게 브라우저의 기본 스타일을 조정

2.3 시스템 다크 모드 감지

MUI에서도 window.matchMedia를 활용하여 시스템 다크 모드 설정을 감지할 수 있다.

import React, { useEffect, useState } from "react";
import { createTheme, ThemeProvider, CssBaseline } from "@mui/material";

function App() {
  const [isDarkMode, setIsDarkMode] = useState(
    window.matchMedia("(prefers-color-scheme: dark)").matches
  );

  useEffect(() => {
    const listener = (e) => setIsDarkMode(e.matches);
    const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");
    darkModeQuery.addEventListener("change", listener);
    return () => darkModeQuery.removeEventListener("change", listener);
  }, []);

  const theme = createTheme({
    palette: {
      mode: isDarkMode ? "dark" : "light",
    },
  });

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <p>현재 모드: {isDarkMode ? "다크 모드" : "라이트 모드"}</p>
    </ThemeProvider>
  );
}

export default App;

2.4 MUI 다크 모드 flicker 현상 방지(SSR 환경)

MUI에서 다크 모드 플리커 현상(Flicker)은 초기 렌더링 시 클라이언트와 서버 간 테마 불일치 또는 초기 테마 로드 지연으로 인해 발생한다. 다크 모드로 지정한 웹 페이지에서 새로고침을 하면 라이트 모드로 보여졌다가 다크 모드로 변하는 이른바 깜빡거리는 현상이 벌어지는 것이다.

mui useMediaQuery를 쓰거나 css 변수 기능을 통해 플리커를 방지 할 수 있다.

useMediaQuery

import React from "react";
import { ThemeProvider, createTheme, CssBaseline, useMediaQuery } from "@mui/material";

function App() {
  const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");

  const theme = React.useMemo(() => {
    return createTheme({
      palette: {
        mode: prefersDarkMode ? "dark" : "light",
      },
    });
  }, [prefersDarkMode]);

  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <div>플리커 없는 다크 모드</div>
    </ThemeProvider>
  );
}

export default App;

CSS variables

theme.applyStyles를 활용

 import Card from '@mui/material/Card';

 function App() {
   return (
     <Card
       sx={(theme) => ({
-        backgroundColor: theme.palette.mode === 'dark' ? '#000' : '#fff',
-        '&:hover': {
-          backgroundColor: theme.palette.mode === 'dark' ? '#333' : '#f5f5f5',
-        },
+        backgroundColor: '#fff',
+        '&:hover': {
+          backgroundColor: '#f5f5f5',
+          ...theme.applyStyles('dark', {
+            backgroundColor: '#333',
+          }),
+        },
+        ...theme.applyStyles('dark', {
+          backgroundColor: '#000',
+        }),
       })}
     />
   );
 }
profile
안녕하세요. 반갑습니다. 건강하세요.

0개의 댓글