오랫동안 밝은 모니터 화면을 들여다보면 눈이 쉽게 피로해진다.
나 역시 모니터 기본 밝기와 명암을 기본값보다 많이 내려놓은 상태로 설정하고 웹사이트에 다크모드가 있다면 무조건 사용하는 편이다.
그래서 현재 진행하는 프로젝트에 다크모드를 넣으면 좋지 않을까 생각을 하게 되어 ThemeProvider와 전역 상태관리 라이브러리를 사용하여 다크모드를 구현할 것이다.
ThemeProvider란 Styled-Component에 들어있는 컴포넌트로써 ThemeProvider를 사용하면 하위에 있는 모든 태그들의 스타일에 영향을 줄 수 있어 전역적으로 스타일링을 해줄 수 있다.
• 환경설정
styled-component
npm install --save styled-components
# with ts
npm i --save-dev @types/styled-components
react-router-dom
npm install react-router-dom
# with ts
npm i react-router-dom @types/react-router-dom
zustand
npm install zustand
우선 다크모드와 라이트모드 각각에 원하는 스타일을 주기위해 설정을 한다.
// theme.ts
export const lightTheme = {
bgColor: "#fdfaf7",
textColor: "#333333",
inputBgColor: "#f9f9f9",
toggleColor: "#cbb5dc",
rgbaLight: "rgba(0,0,0,0.1)",
rgbaMedium: "rgba(0,0,0,0.5)",
rgbaBold: "rgba(0,0,0,1)"
};
export const darkTheme = {
bgColor: "#121212",
textColor: "#DEDEDE",
inputBgColor: "#000000",
toggleColor: "#e0cde3",
rgbaLight: "rgba(255,255,255,0.1)",
rgbaMedium: "rgba(255,255,255,0.5)",
rgbaBold: "rgba(255,255,255,1)"
};
export const theme = {
darkTheme,
lightTheme,
};
각 테마에 어울리게 원하는 컬러를 설정하여 string 형식으로 작성해 준다.
그리고 TypeScript에서는 type을 따로 설정을 해주지 않으면 오류가 발생하기에 아래와 같이 설정해 준다.
// styled.d.ts
import "styled-components";
declare module "styled-components" {
export interface DefaultTheme {
bgColor: string;
textColor: string;
inputBgColor: string;
toggleColor: string;
rgbaLight: string;
rgbaMedium: string;
rgbaBold: string;
}
}
이제 ThemeProvider를 최상위 컴포넌트에서 설정을 해준다.
// App.tsx
import React, {useEffect} from 'react';
import {Route, Routes} from "react-router-dom";
import {ThemeProvider} from "styled-components";
import MainHome from './home/MainHome';
import SignIn from "./member/SignIn";
import SignUp from "./member/SignUp";
import {darkTheme, lightTheme} from "./styles/theme";
import useThemeToggleStore from "./stores/useThemeToggleStore";
function App() {
const {themeMode, setThemeMode} = useThemeToggleStore();
return (
<>
<ThemeProvider theme={themeMode ? darkTheme : lightTheme } >
<Routes>
<Route path="/" element={<MainHome />} />
<Route path="/signIn" element={<SignIn />} />
<Route path="/signUp" element={<SignUp />} />
</Routes>
</ThemeProvider>
</>
);
}
export default App;
ThemeProvider를 Routes를 감싸주듯이 코드를 작성해 주고 ThemeProvider에 상태값을 전달함으로써 테마에 맞게 스타일이 전역적으로 적용이 될 것이다.
상태값을 넣어주는 것은 토글 버튼을 따로 하나 생성해 주고 그 버튼을 누를 때마다 전역 상태관리 라이브러리를 통해 상태를 전달하여 넣어주는 형식으로 만들어 주었다.
// useThemeToggleStore.ts
import {create} from "zustand";
interface themeToggleStore {
themeMode: boolean;
setThemeMode: (toggle: boolean) => void;
}
const useThemeToggleStore = create<themeToggleStore>((set) => ({
themeMode: false,
setThemeMode: (toggle: boolean) =>
set((state: {themeMode: boolean}) => ({
themeMode: (state.themeMode = toggle),
})),
}));
export default useThemeToggleStore;
상태관리 라이브러리는 zustand를 사용하였고 위와 같이 코드를 작성하여 사용하였다.
기본 상태값은 false로 false일 경우 ThemeProvider에서는 삼항연산자를 통해 라이트모드로 적용이 된다.
그리고 상태값이 true로 변경이 되면 다크모드로 되는 방식으로 동작이 된다.
// ThemeModeToggle.tsx
import React from "react";
import useThemeToggleStore from "../stores/useThemeToggleStore";
const ThemeModeToggle = () => {
const {themeMode, setThemeMode} = useThemeToggleStore();
return (
<div>
<button onClick={() => setThemeMode(!themeMode)}>
다크모드
</button>
</div>
);
};
export default ThemeModeToggle;
토글 버튼은 간단하게 버튼 클릭 시 상태값이 변경되게 끔만 작성하였다.
그리고 컴포넌트 별로 지정을 해도 되지만 정말 전역적으로 스타일을 변경해주고 싶다면 아래와 같은 코드를 추가해 주면 된다.
// GlobalStyles.ts
import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
body {
background: ${({ theme }) => theme.bgColor};
color: ${({ theme }) => theme.textColor};
display: block;
height: 100%;
width: 100%;
}
`;
작성한 GlobalStyle을 ThemeProvider 안에 넣어준다.
// App.tsx
import React, {useEffect} from 'react';
import {Route, Routes} from "react-router-dom";
import {ThemeProvider} from "styled-components";
import MainHome from './home/MainHome';
import SignIn from "./member/SignIn";
import SignUp from "./member/SignUp";
import {GlobalStyle} from "./styles/GlobalStyles";
import {darkTheme, lightTheme} from "./styles/theme";
import useThemeToggleStore from "./stores/useThemeToggleStore";
function App() {
const {themeMode, setThemeMode} = useThemeToggleStore();
return (
<>
<ThemeProvider theme={themeMode ? darkTheme : lightTheme } >
<GlobalStyle /> // 추가!
<Routes>
<Route path="/" element={<MainHome />} />
<Route path="/signIn" element={<SignIn />} />
<Route path="/signUp" element={<SignUp />} />
</Routes>
</ThemeProvider>
</>
);
}
export default App;
여기까지 해준다면 아래와 같이 다크모드가 동작이 될 것이다.
하나 문제가 있다 페이지가 이동되거나 새로고침이 되면 다크모드에서 라이트모드로 자동으로 바뀐다는 점..
기본 상태값이 라이트모드이기에 페이지 이동 및 새로고침을 하면 기본값으로 변경이 되는 것 같다.
그래서 해결책으로 값을 localStorage에 저장을 시켜주기로 했다.
버튼 컴포넌트와 ThemeProvider가 있는 컴포넌트에 각각 아래와 같은 코드를 추가하였다.
// ThemeModeToggle.tsx
import React from "react";
import useThemeToggleStore from "../stores/useThemeToggleStore";
const ThemeModeToggle = () => {
const {themeMode, setThemeMode} = useThemeToggleStore();
const onChangeToggle = ():void => {
setThemeMode(!themeMode)
window.localStorage.setItem("theme", `${!themeMode}`);
} // 추가!
return (
<div>
<button onClick={() => onChangeToggle()}> // 변경!
다크모드
</button>
</div>
);
};
export default ThemeModeToggle;
// App.tsx
import React, {useEffect} from 'react';
import {Route, Routes} from "react-router-dom";
import {ThemeProvider} from "styled-components";
import MainHome from './home/MainHome';
import SignIn from "./member/SignIn";
import SignUp from "./member/SignUp";
import {darkTheme, lightTheme} from "./styles/theme";
import useThemeToggleStore from "./stores/useThemeToggleStore";
function App() {
const {themeMode, setThemeMode} = useThemeToggleStore();
useEffect(() => {
const localTheme:string|null = window.localStorage.getItem("theme");
if(localTheme === 'false') {
setThemeMode(false);
} else {
setThemeMode(true);
}
}, []) // 추가!
return (
<>
<ThemeProvider theme={themeMode ? darkTheme : lightTheme } >
<GlobalStyle />
<Routes>
<Route path="/" element={<MainHome />} />
<Route path="/signIn" element={<SignIn />} />
<Route path="/signUp" element={<SignUp />} />
</Routes>
</ThemeProvider>
</>
);
}
export default App;
위와 같이 코드를 추가해 주고 새로고침 및 페이지 이동을 하더라도 테마가 기본값으로 변하지 않을 것이다.
추가로 컴포넌트 별로 스타일을 지정해주고자 한다면 theme.ts 와 styled.d.ts에 원하는 스타일과 값을 작성해 주고 GlobalStyle에서 작성한 것과 같이 각 컴포넌트 별로 지정한 styled-component에 ${({theme}) => theme.설정값} 형태로 코드를 추가해 주면 된다.
'[개인프로젝트] 개발 공부' 카테고리의 다른 글
[프로젝트] 6. Spring Security + JWT로 로그인 구현하기 (2) (3) | 2024.03.06 |
---|---|
[프로젝트] 5. Spring Security + JWT로 로그인 구현하기 (1) (0) | 2024.03.01 |
[프로젝트] 4. React 상태 관리 라이브러리 - Zustand (1) | 2024.02.27 |
[프로젝트] 2. SPRINGBOOT 3.0 + Query DSL (0) | 2024.01.06 |
[프로젝트] 1. SPRINGBOOT + REACT 연동하기 (0) | 2023.12.13 |