🍀 인터랙티브 웹에서 한 번쯤은 사용해본 다크모드, Styled-components를 사용해서 구현해봤습니다.
중간에 삽질도 많이 했습니다만... 미래의 나 그리고 누군가를 위해 메모해보려 합니다.
테마 관리를 위해 리액트 Context API를 사용했습니다.
styled-reset 라이브러리는 Styled-components와 연결된 라이브러리로, 여러 브라우저의 디폴트 스타일을 제거해줍니다.
npm i styled-components styled-reset
npm i --save-dev @types/styled-components
공식문서에도 나왔듯, 우선 DefaultTheme의 속성을 설정했습니다.
ThemeProvider의 theme 속성에 들어갈 객체의 인터페이스를 정리하는 단계입니다.
이 단계를 생략하고 별도로 정의한 커스텀 theme 인터페이스를 사용하는 삽질을 범했습니다.
토글 버튼을 아무리 눌러도 UI가 바뀌지 않았다는 슬픈 전설이 있습니다 😂
✅ styled.d.ts
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
bgColor: string;
textColor: string;
borderColor: string;
}
}
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
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 };
테마 모드와 테마 모드 변경을 위한 함수를 관리하는 컨텍스트를 생성했습니다.
디폴트 모드는 라이트 모드입니다.
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.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 };
모드를 변경하는 실제 버튼 컴포넌트입니다.
클릭 이벤트 핸들러 함수로 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;
`;
테마 컨텍스트(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;
토글버튼을 누르면 해당 모드로 색이 변하는 것을 확인할 수 있습니다.