🍀 인터랙티브 웹에서 한 번쯤은 사용해본 다크모드, Styled-components를 사용해서 구현해봤습니다.
중간에 삽질도 많이 했습니다만... 미래의 나 그리고 누군가를 위해 메모해보려 합니다.
테마 관리를 위해 리액트 Context API를 사용했습니다.

📍 라이브러리 설치

styled-reset 라이브러리는 Styled-components와 연결된 라이브러리로, 여러 브라우저의 디폴트 스타일을 제거해줍니다.

npm i styled-components styled-reset
npm i --save-dev @types/styled-components

📍 테마 설정

styled.d.ts

공식문서에도 나왔듯, 우선 DefaultTheme의 속성을 설정했습니다.
ThemeProvider의 theme 속성에 들어갈 객체의 인터페이스를 정리하는 단계입니다.
이 단계를 생략하고 별도로 정의한 커스텀 theme 인터페이스를 사용하는 삽질을 범했습니다.
토글 버튼을 아무리 눌러도 UI가 바뀌지 않았다는 슬픈 전설이 있습니다 😂

✅ styled.d.ts

import "styled-components";

declare module "styled-components" {
  export interface DefaultTheme {
    bgColor: string;
    textColor: string;
    borderColor: string;
  }
}

theme.ts

ThemeProvider 내부에서 사용 가능한 테마 목록을 작성했습니다.
다크 모드인 경우에는 darkTheme에 해당하는 속성이 반영된 UI가 보이고,
라이트 모드인 경우에는 lightTheme에 해당하는 속성이 반영된 UI가 보입니다.

✅ theme.ts

import { DefaultTheme } from "styled-components";

const lightTheme: DefaultTheme = {
  bgColor: "red",
  textColor: "black",
  borderColor: "1px solid #EAEAEA",
};

const darkTheme: DefaultTheme = {
  bgColor: "purple",
  textColor: "yellow",
  borderColor: "1px solid #2c2d33",
};

export { lightTheme, darkTheme };

GlobalStyle.tsx

다크, 라이트 모드 변경에 따른 배경 및 글자 색상을 전역에서 관리할 수 있습니다.

✅ GlobalStyle.tsx

import { createGlobalStyle } from "styled-components";
import reset from "styled-reset";

const GlobalStyle = createGlobalStyle`
  ${reset}

  body {
    width: 100%;
    height: 100%;
    margin: 0 auto;
    background-color: ${({ theme }) => theme.bgColor};
    color: ${({ theme }) => theme.textColor};
  }
`;

export { GlobalStyle };

📍 Context API

테마 모드와 테마 모드 변경을 위한 함수를 관리하는 컨텍스트를 생성했습니다.
디폴트 모드는 라이트 모드입니다.
Styled-components 라이브러리로부터 ThemeProvider 컴포넌트를 불러와 theme 속성에 lightTheme 또는 darkTheme을 전달합니다.

✅ ThemeModeProvider.ts

import { createContext, ReactNode, useState } from "react";
import { ThemeProvider } from "styled-components";
import { darkTheme, lightTheme } from "../theme/theme";

type Props = {
  children?: ReactNode;
};

export type ThemeModeType = "light" | "dark";

type ThemeContextProps = {
  themeMode: ThemeModeType;
  setThemeMode: React.Dispatch<React.SetStateAction<ThemeModeType>>;
};

const ThemeContext = createContext<ThemeContextProps>({
  themeMode: "light",
  setThemeMode: () => {},
});

export default function ThemeModeProvider({ children }: Props) {
  const [themeMode, setThemeMode] = useState<ThemeModeType>("light");
  const targetTheme = themeMode === "light" ? lightTheme : darkTheme;

  return (
    <ThemeContext.Provider value={{ themeMode, setThemeMode }}>
      <ThemeProvider theme={targetTheme}>{children}</ThemeProvider>
    </ThemeContext.Provider>
  );
}

export { ThemeContext };

📍 useTheme 커스텀 훅

테마를 변경하는 훅을 별도로 만들었습니다.
커스텀 훅이 리턴하는 값을 타입으로 선언하는 부분이 어려웠습니다.

✅ useTheme.ts

import { useCallback, useContext } from "react";
import { ThemeContext, ThemeModeType } from "../context/ThemeModeProvider";

type UseThemeProps = [themeMode: ThemeModeType, toggleTheme: () => void];

function useTheme(): UseThemeProps {
  const { themeMode, setThemeMode } = useContext(ThemeContext);
  
  const toggleTheme = useCallback(() => {
    if (themeMode === "light") setThemeMode("dark");
    else setThemeMode("light");
  }, [themeMode]);

  return [themeMode, toggleTheme];
}

export { useTheme };

📍 ToggleTheme.tsx

모드를 변경하는 실제 버튼 컴포넌트입니다.
클릭 이벤트 핸들러 함수로 useTheme 훅에서 불러온 toggleTheme 함수를 사용했습니다.

✅ ToggleTheme.tsx

import styled from "styled-components";
import { useTheme } from "../hooks/useTheme";

function ToggleTheme() {
  const [themeMode, toggleTheme] = useTheme();

  return <ToggleWrapper onClick={toggleTheme}>{themeMode}</ToggleWrapper>;
}
export default ToggleTheme;

const ToggleWrapper = styled.button`
  position: fixed;
  z-index: 20;
  top: 10px;
  right: 10px;
  width: 100px;
  height: 50px;
  border-radius: 30px;
  background-color: ${({ theme }) => theme.bgColor};
  border: ${({ theme }) => theme.borderColor};
  cursor: pointer;
`;

📍 App.tsx

테마 컨텍스트(ThemeModeProvider) 안에 GlobalStyle과 토글버튼(ToggleTheme)의 2가지 children 컴포넌트를 선언했습니다.
토글버튼을 클릭하면 선택된 모드에 따라 UI가 바뀝니다.

import ToggleTheme from "./components/ToggleTheme";
import ThemeModeProvider from "./context/ThemeModeProvider";
import { GlobalStyle } from "./theme/GlobalStyle";

function App() {
  return (
    <ThemeModeProvider>
      <GlobalStyle />
      <ToggleTheme />
    </ThemeModeProvider>
  );
}

export default App;

💁🏻 결과

토글버튼을 누르면 해당 모드로 색이 변하는 것을 확인할 수 있습니다.

profile
기록해서 남길래요

0개의 댓글

Powered by GraphCDN, the GraphQL CDN