안녕하세요! 오늘은 TypeScript React에서 Recoil을 이용한 다크모드 적용하기를 주제로 작성해보도록 하겠습니다. 흔히 다크모드는 네이버, 유튜브, 페이스북 등에서 다양하게 지원되고 있는 테마 방식입니다. 이 다크모드를 이번 글에서 구현해볼것이며, 전역 상태관리 도구로는 Recoil을 사용하도록 하겠습니다.
프로젝트를 생성한다음, src/styles 폴더 안에다가 colors.scss 파일을 만들어서 아래의 내용을 추가합니다. 그리고, 해당 scss 파일을 root 파일에다가 import를 해주세요! 컬러 변수의 방식은 css 변수 방식을 사용하도록 하겠습니다.
/* colors.scss */
:root {
--red: #f5160a;
--orange: #e67e22;
--yellow: #f5e62d;
--green: #27ae60;
--blue: #0984e3;
--skyBlue: #00a8ff;
--navy: #000080;
--purple: #841dfa;
--mint: #00d2d3;
--pink: #e84393;
--lighterGray: #dfe6e9;
--white: #ffffff;
--black: #000000;
--gray: #b2bec3;
--snow: #dff9fb;
--primary: #1b60ff;
--lighterGreen: #55efc4;
--navbar: #2c3e50;
}
// Wrapper 클래스가 'light'일시 var(--theme)와 var(--contrast)의 컬러가 다르게 적용됩니다.
.light {
--theme: #f2f2f2;
--contrast: #000000;
}
// Wrapper 클래스가 'dark'일시 var(--theme)와 var(--contrast)의 컬러가 다르게 적용됩니다.
.dark {
--theme: #212121;
--contrast: #f2f2f2;
}
Recoil 파일을 만져주기 전에, 테마에 대한 enum들을 선언해주도록 하겠습니다. 저는 다크와 라이트를 선언해주었습니다.
/* src/enum/ThemeEnum.ts */
export enum ThemeEnums {
LIGHT = 0,
DARK = 1,
};
저는 src/recoil 디렉토리에 Theme.ts 파일을 생성한다음, 아래의 코드를 추가해주었습니다.
Recoil을 사용하기 전, RecoilRoot 설정을 하는것을 잊지 마세요!
import { atom } from 'recoil';
import { ThemeEnums } from 'enum/ThemeEnum';
const { LIGHT, DARK } = ThemeEnums;
export const getTheme = (): ThemeEnums => {
const theme: number = Number(localStorage.getItem('theme'));
if (theme === DARK) {
return DARK;
}
// localStorage에 있는 값이 DARK가 아니라면, 모든 경우에도 LIGHT를 return 합니다.
return LIGHT;
}
export const themeMode = atom<ThemeEnums>({
key: 'themeMode',
default: getTheme(),
// 기본값은 localStorage에 있는 값에 따라서 설정 해줍니다.
});
이제 Enum과 Recoil 파일들을 모두 설정해주었으니, 조건에 따른 className을 설정해주기 위해서 App.tsx 코드를 아래와 같이 수정해주었습니다.
// App.tsx
import React from 'react';
import { Switch } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { ThemeEnums } from 'enum/ThemeEnum';
import { themeMode } from 'recoil/Theme';
const App = (): JSX.Element => {
const theme: ThemeEnums = useRecoilValue(themeMode);
// recoil의 get state만 가져옵니다.
const { LIGHT } = ThemeEnums;
return (
<div className={theme === LIGHT ? 'light' : 'dark'}>
{/* 현재의 테마가 라이트모드일시, 클래스 이름은 'light' 아니면 'dark' */}
<Switch>
{/* react-router-dom 세팅들을 해주세요! */}
</Switch>
</div>
);
}
export default App;
이제 위의 사진처럼 버튼을 클릭하여 다크모드 / 라이트모드가 토글 형식으로 되도록 기능을 구현할 컴포넌트를 생성하도록 하겠습니다! (위의 사진은 그저 예시 사진입니다.)
src/components 디렉토리에 ToggleTheme 컴포넌트를 생성한뒤, ToggleTheme.tsx 파일에 아래의 코드를 추가해주세요!
// ToggleTheme.tsx
import React, { useCallback } from 'react';
import { IoMdSunny } from 'react-icons/io';
import { FaMoon } from 'react-icons/fa';
import { useRecoilState } from 'recoil';
import { themeMode } from 'recoil/Theme';
import { ThemeEnums } from 'enum/ThemeEnum';
import './ToggleTheme.scss';
const ToggleTheme = (): JSX.Element => {
const [theme, setTheme] = useRecoilState<ThemeEnums>(themeMode);
const { LIGHT, DARK } = ThemeEnums;
const handleChangeTheme = useCallback((): void => {
if (theme === DARK) {
localStorage.setItem('theme', LIGHT);
setTheme(LIGHT);
return;
}
localStorage.setItem('theme', DARK);
setTheme(DARK);
}, [DARK, LIGHT, setTheme, theme]);
return (
<div
className='ToggleTheme'
onClick={handleChangeTheme}
>
{
theme === LIGHT ? <FaMoon /> : <IoMdSunny />
// 테마가 라이트모드 / 다크모드일때마다 아이콘을 다르게 렌더링 해줍니다.
// 취향에 따라 아이콘을 설정해주세요 :)
}
</div>
);
};
export default ToggleTheme;
아래는 ToggleTheme.scss 파일입니다.
.ToggleTheme {
width: 65px;
height: 65px;
display: flex;
display: -webkit-flex;
justify-content: center;
align-items: center;
background-color: var(--theme);
/* colors.scss 파일에 설정해두었던 클래스 이름 조건에 따라 theme 컬러가 다르게 적용됩니다. */
border: 2px solid var(--yellow);
color: var(--yellow);
border-radius: 15px;
font-size: 2rem;
cursor: pointer;
transition: all 0.15s ease-in-out;
&:hover {
background-color: var(--yellow);
color: var(--white);
}
}
지금까지 ToggleTheme 컴포넌트 말고도, 다른 컴포넌트 파일에서 컬러 변수 (theme, contrast 등등) 들을 골고루 썼다면 다크/라이트모드의 변경이 눈에 잘 보일것 입니다.
이제 위에서 작성했던 코드들을 바탕으로, 테스트가 잘 되는지 확인해보도록 하겠습니다. 저같은 경우에는, 개인 포트폴리오 사이트에 해당 코드를 넣어서 실험해봤습니다.
저는 위의 사진들처럼 다크 / 라이트모드가 올바르게 작동되는 모습을 볼 수 있었습니다. 여러분들은 위와 같이 올바르게 작동하나요?
제가 다크모드/라이트모드를 프로젝트에 쉽게 적용하기 위해선 프로젝트 초기의 컬러 사용법을 추후의 테마를 생각하면서 짜는것이 중요하다고 생각했습니다. 저의 포트폴리오 사이트의 경우에는 규모가 크지 않아서 바꾸는데 시간이 오래걸리진 않았지만, 규모가 커질수록 방식을 바꾸는데 어려울 것이라고 생각합니다.
이렇게 글을 마무리 하도록 하겠습니다. 궁금한점이 있으시다면 댓글로 달아주세요! 긴 글 읽어주셔서 감사합니다! 😀