<React> 다크모드 구현하기

yezee·2022년 11월 17일
0

React

목록 보기
8/23
post-thumbnail

유저로서 페이지 기능에서 젤 신기했던게 다크모드 였다~🤹🏻
나도 개발자 되면 간지나게 이런거 만들고 싶었다 그래서 만들었따!
(클론한 밀리의 서재에는 다크모드가 없다는게 함정 ㅋㅋㅋ)

코드분석

전역에서 다크모드를 사용할 수 있도록 useContext를 이용했습니다

전역에서 다크모드를 사용할 수 있도록 라우터 페이지에서 useContext를 이용해서 ThmeContext를 만들었습니다
초기값으로 "light"를 넣어주고 버튼으로 "light"<=>"dark"로 토글이 되게 만들기 위해
토글함수 tottleTheme를 만들었습니다

//Router.js
export const ThemeContext=createContext("light")

function Router(){
const [theme,setTheme]=useState("light")
const toggleTheme=()=>{
setTheme(cur=>(cur =="light"? "dark":"light"))
 }

return(
  <ThemeContext.Provider value={{toggleTheme,theme}}>
  	<div id={theme}>
      <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/login" element={<Login />} />
        	{/*생략*/}
          <Route path="/bookDetail" element={<BookDetail />} />
        </Routes>
    </div>
  </ThemeContext.Provider>
 )
}

scss

다음으로는 전역으로 사용하는 common.scss에 각각의 모드에서 어떻게 동작할지 넣어줘야한다
이때 핵심은 css 적용우선순위를 이용해서 다크모드 시 모든 css를 이기고(?) 다크모드의 scss가 보여지도록 한다

<div id={theme}><Routes>...</Routes></div>
css 적용우선순위의 상위 포식자인 id를 이용해서 상태값인 theme이==light모드일때와 theme이==dark모드일때를 작성해준다

/*common.scss*/

#light{
background-color:white;
color:black;
}
#dark{
background-color:black;
color:white;
}

근데 이렇게 했다고해서 모든 부분이 원하는 방식으로 전환되지 않을 수 있다
그럼 부분적으로 손수 커스텀을 해주어야한다
#dark.클래스네임이름 또는 태그이름을 붙여 다크모드일때 어떻게 보일지 조정해준다

예를들어) header에 로그아웃 버튼이 있는데 원래 색상이 검은색이였다 따라서 다크모드일때는 반대로 흰색이 되어 버튼이 보일 수 있도록 별개로 조절이 필요하다

/*header.scss*/

#dark Button {
  background-color: white;
  color: black;
}

토글 버튼

저는 헤더부분에 해모양의 이모티콘일때는 "light모드" 달모양의 이모티콘일때는 "dark모드"가 될 수 있도록 코드를 만들었습니다
Router.js에서 useContext를 통해 theme과 toggleTheme을 가져왔습니다
onClick={toggleTheme}을 넣어 이모티콘을 누르면 토글이 동작할 수 있도록해주고
삼항연산자를 이용해서 theme=="light"일 때 해 이모티콘을 반대의 경우 달 이모티콘이 나타날 수 있도록 해줬습니다

//header.jsx
import React, { useContext, useState } from 'react';
import { ThemeContext } from '../../pages/Router.js'; //themeContext 가져옴
import { BsFillSunFill } from 'react-icons/bs'; //해 이모티콘
import { FaRegMoon } from 'react-icons/fa'; //달 이모티콘

function Header() {
  const { theme } = useContext(ThemeContext); //themecontext를 통해 theme가져옴
  const { toggleTheme } = useContext(ThemeContext); //themecontext를 통해 toggleTheme가져옴
  
  	return(
    
    			  {/*생략*/}
    
     <div className="head-right">
        <div onClick={toggleTheme}>
        {theme === 'light' ? <BsFillSunFill /> : <FaRegMoon />}
     </div>
      {/*생략*/}
      </div>
  
  )
}

코드 리팩토링

createContext는 내부에 공유하길 원하는 데이터의 초깃값을 넣어두고 value 변수로 묶어줍니다 이 때 value는 객체이므로 리렌더링의 주범이 되므로 useMemo로 캐싱주는 것이 좋습니다 안 그러면 나중에 이 데이터를 쓰는 모든 컴포넌트가 매번 리렌더링됩니다
해결책 : useMemo,useCallback => 객체(함수)자체를 Memoization해준다(값과 콜백을 메모리 어딘가에 저장해두고 필요할 때 가져다 쓴다)
useMemo,useCallback이 헷갈려??
: useMemo는 어떠한 "값"을 메모이징하기 위한것 useCallback은 어떠한 "함수"를 메모이징하기 위한 것

해서 toggleTheme함수로 인해 계속 렌더링되지 않도록 useCallback()을 넣어주었습니다
앞으로는 useCallback의 디펜던시로 setTheme을 넣어 theme에 변화가 생겼을 때만 랜더링이 되도록 해주었습니다

export const ThemeContext = createContext('light');

function Router() {
  const [theme, setTheme] = useState('light');
  const toggleTheme = useCallback(() => {
    setTheme(cur => (cur === 'light' ? 'dark' : 'light'));
  }, [setTheme]);

  return (
    <ThemeContext.Provider value={{ toggleTheme,theme }}>
      <div id={theme}>
        <Routes>
          <Route path="/" element={<Home />} />
          		{/*생략*/}
          <Route path="/bookDetail" element={<BookDetail />} />
        </Routes>
      </div>
    </ThemeContext.Provider>
  );
}
profile
아 그거 뭐였지?

0개의 댓글