React 다크모드 / 라이트 모드 구현하기

구구·2023년 2월 2일
0

🪴 시연 모습

헤더에 테마 모드 (다크모드 / 라이트모드) 를 변경할 수 있는 버튼을 구현했습니다.

👉 GitHub 링크



🪴 작성한 코드

react-redux-toolkit 라이브러리를 활용하여 테마모드의 상태값을 전역으로 관리하였습니다.


리듀서 함수 👉 GitHub 코드 링크

  • action과 reducer를 하나의 파일에서 관리함으로써 코드의 가독성을 높여주기 위해 createSlice 를 활용하였습니다.
  • 사용자가 웹페이지에 접근할 때마다 이전에 설정해두었던 테마 모드를 적용시켜주기 위해서 localStorage 에 boolean 값으로 isDarkMode 가 true 인지 false 인지 저장해두는 방식을 택했습니다.
  • document.body.dataset.theme 에 접근하여 ~~
// 파일 경로: src/store/modules/header.js

import { createSlice } from '@reduxjs/toolkit';

const themeModeSlice = createSlice({
  name: 'themeModeReducer',
  initialState: {
    isDarkMode: JSON.parse(localStorage.getItem('isDarkMode')),
  },
  reducers: {
    darkMode: state => {
      localStorage.setItem('isDarkMode', true);
      state.isDarkMode = true;
      document.body.dataset.theme = 'dark';
    },
    lightMode: state => {
      localStorage.setItem('isDarkMode', false);
      state.isDarkMode = false;
      document.body.dataset.theme = 'light';
    },
  },
});

export const { darkMode, lightMode } = themeModeSlice.actions;
export const themeModeReducer = themeModeSlice.reducer;


redux 세팅하기 (store.js) 👉 GitHub 코드 링크

  • Reducer에서 반환된 새로운 state를 Store라는 객체로 정리해 관리해주었습니다.
// 파일 경로: src/store/index.js

import { configureStore } from '@reduxjs/toolkit';
import { themeModeReducer } from './modules/header';
// ... 생략

const store = configureStore({
  reducer: {
    darkMode: themeModeReducer,
    // ... 생략
  },
});

export default store;


redux 세팅하기 (main.jsx) 👉 GitHub 코드 링크

// 파일 경로: src/main.jsx

import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import ScrollTop from './components/ScrollTop';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <BrowserRouter>
    <ScrollTop />
    <Provider store={store}>
      <App />
    </Provider>
  </BrowserRouter>
);


ThemeMode 컴포넌트 👉 GitHub 코드 링크

  • useEffect 를 활용하여 첫 렌더링시 사용자가 이전에 설정해 두었던 테마모드를 체크해줍니다.
  • 테마 모드 아이콘(해/달)을 클릭할 때마다 changeTheme 함수를 호출하여 redux 상태값을 업데이트 해줍니다.
  • CSS 의 keyframes 문법을 활용하여 해와 달 아이콘이 변경될 때마다 180deg 회전하도록 애니메이션을 구현하였습니다.
// 파일 경로: src/layout/Header/RightIcons/ThemeMode/index.jsx

import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { darkMode, lightMode } from '../../../../store/modules/header';  // redux 모듈 파일
import { HiMoon } from 'react-icons/hi';  // 달 아이콘
import { BsFillSunFill } from 'react-icons/bs';  // 해 아이콘
import styled from 'styled-components';

const ThemeMode = () => {
  const isDarkMode = useSelector(state => state.darkMode.isDarkMode);
  const dispatch = useDispatch();

  useEffect(() => {  // 첫 렌더링시 체크
    isDarkMode ? dispatch(darkMode()) : dispatch(lightMode());
  }, []);

  const changeTheme = e => {
    e.target.className = 'theme-mode-change setting-hover';
    isDarkMode ? dispatch(lightMode()) : dispatch(darkMode());
  };

  return (
    <ThemeModeContainer className='theme-mode-container' onClick={changeTheme}>
      <div className='setting-hover'>{isDarkMode ? <HiMoon /> : <BsFillSunFill />}</div>
    </ThemeModeContainer>
  );
};

const ThemeModeContainer = styled.div`
  @keyframes themeChange {
    from {
      width: 10px;
      height: 10px;
      transform: rotate(180deg);
    }
    to {
      width: 24px;
      height: 24px;
    }
  }
  .theme-mode-change {
    svg {
      animation: themeChange 0.1s linear;
    }
  }
`;

export default ThemeMode;

0개의 댓글