[React.js] 다크모드 (Emotion.js + Next.js + TypeScript)

https://velog.io/@ongddree/블로그만들기-다크모드-구현
게시물을 통해 참고를 하였고, 이 게시물은 다른 프로젝트에 구현한 것을 회고합니다.
🎯 TL;DR
- 
다크모드는 프로젝트 시작 후 빠르게 구현하는게 좋습니다. (꿀팁🍯)
 
- 
헤더에 라이트모드/다크버튼의 토글을 통해 테마를 변경한다.
 
- 
페이지를 이동, 새로고침 해도 적용한 테마가 유지되게 한다. (Context API + localStorage)
 
1️⃣ theme 색상 지정
// styles/theme.ts
export const lightTheme = {
  MAIN: 'black',
  SUB: 'white',
  BACKGROUND: '#fdfdff',
};
export const darkTheme = {
  MAIN: 'white',
  SUB: 'black',
  BACKGROUND: '#202124',
};
export type ColorTheme = typeof lightTheme;
- 
프로젝트의 특성에 맞게 MAIN, SUB는  글자의 색상, BACKGROUND는 배경색을 지정합니다. (더 필요한 색상이 있으면 추가합니다!)
 
- 
typeof를 통해 ColorTheme type을 export 해줍니다.
 
2️⃣ useDarkMode() 구현
- 
Next.js에서는 렌더링이 일어나기 전에 localStorage, window 등에 접근하면 에러가난다. 그래서 useEffect내부에서 window.localStorage.getItem('theme')를 통해 새로고침 or 첫 렌더링시 테마를 결정하게 된다. (default value는 lightTheme이다.)
 
- 
처음으로 localStorage.setItem을 하는 시기는 라이트모드 -> 다크모드를 처음했을때다.
 
// hooks/useDarkMode.ts
import { useEffect, useState } from 'react';
import { lightTheme, darkTheme, ColorTheme } from '../styles/theme';
export const useDarkMode = () => {
  // 1. 초기 colorTheme은 lightTheme를 가진다.
  const [colorTheme, setColorTheme] = useState<ColorTheme>(lightTheme);
  
  // 4. state의 값도 변경 + local 저장 값도 변경
  const setMode = (mode: ColorTheme) => {
    mode === lightTheme
      ? window.localStorage.setItem('theme', 'light')
      : window.localStorage.setItem('theme', 'dark');
    setColorTheme(mode);
  };
  // 3. 사용자가 toggleColorTheme을 하면 setMode를 통해 기존의 colorTheme과 반대 값을 저장한다.
  const toggleColorTheme = () => {
    colorTheme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
  };
  // 2. 마운트 되면 localStorage에 'theme'이 있는지 찾는다.
  // - 새로고침시 다크모드/라이트모드 바로 적용
  // - 페이지 로드가 처음이면 이 과정은 무시된다
  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    if (localTheme !== null) { // localTheme이 존재한다면
      if (localTheme === 'dark') {
        setColorTheme(darkTheme);
      } else {
        setColorTheme(lightTheme);
      }
    }
  }, []);
  return { colorTheme, toggleColorTheme };
};
3️⃣ darkmode 상태 관리
다크모드는 프로젝트 시작 후 빠르게 구현하는게 좋습니다. (꿀팁🍯)
헤더에 라이트모드/다크버튼의 토글을 통해 테마를 변경한다.
페이지를 이동, 새로고침 해도 적용한 테마가 유지되게 한다. (Context API + localStorage)
// styles/theme.ts
export const lightTheme = {
  MAIN: 'black',
  SUB: 'white',
  BACKGROUND: '#fdfdff',
};
export const darkTheme = {
  MAIN: 'white',
  SUB: 'black',
  BACKGROUND: '#202124',
};
export type ColorTheme = typeof lightTheme;
- 
프로젝트의 특성에 맞게
MAIN,SUB는 글자의 색상,BACKGROUND는 배경색을 지정합니다. (더 필요한 색상이 있으면 추가합니다!) - 
typeof를 통해 ColorTheme type을 export 해줍니다. 
2️⃣ useDarkMode() 구현
- 
Next.js에서는 렌더링이 일어나기 전에 localStorage, window 등에 접근하면 에러가난다. 그래서 useEffect내부에서 window.localStorage.getItem('theme')를 통해 새로고침 or 첫 렌더링시 테마를 결정하게 된다. (default value는 lightTheme이다.)
 
- 
처음으로 localStorage.setItem을 하는 시기는 라이트모드 -> 다크모드를 처음했을때다.
 
// hooks/useDarkMode.ts
import { useEffect, useState } from 'react';
import { lightTheme, darkTheme, ColorTheme } from '../styles/theme';
export const useDarkMode = () => {
  // 1. 초기 colorTheme은 lightTheme를 가진다.
  const [colorTheme, setColorTheme] = useState<ColorTheme>(lightTheme);
  
  // 4. state의 값도 변경 + local 저장 값도 변경
  const setMode = (mode: ColorTheme) => {
    mode === lightTheme
      ? window.localStorage.setItem('theme', 'light')
      : window.localStorage.setItem('theme', 'dark');
    setColorTheme(mode);
  };
  // 3. 사용자가 toggleColorTheme을 하면 setMode를 통해 기존의 colorTheme과 반대 값을 저장한다.
  const toggleColorTheme = () => {
    colorTheme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
  };
  // 2. 마운트 되면 localStorage에 'theme'이 있는지 찾는다.
  // - 새로고침시 다크모드/라이트모드 바로 적용
  // - 페이지 로드가 처음이면 이 과정은 무시된다
  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    if (localTheme !== null) { // localTheme이 존재한다면
      if (localTheme === 'dark') {
        setColorTheme(darkTheme);
      } else {
        setColorTheme(lightTheme);
      }
    }
  }, []);
  return { colorTheme, toggleColorTheme };
};
3️⃣ darkmode 상태 관리
Next.js에서는 렌더링이 일어나기 전에 localStorage, window 등에 접근하면 에러가난다. 그래서 useEffect내부에서 window.localStorage.getItem('theme')를 통해 새로고침 or 첫 렌더링시 테마를 결정하게 된다. (default value는 lightTheme이다.)
처음으로 localStorage.setItem을 하는 시기는 라이트모드 -> 다크모드를 처음했을때다.
// hooks/useDarkMode.ts
import { useEffect, useState } from 'react';
import { lightTheme, darkTheme, ColorTheme } from '../styles/theme';
export const useDarkMode = () => {
  // 1. 초기 colorTheme은 lightTheme를 가진다.
  const [colorTheme, setColorTheme] = useState<ColorTheme>(lightTheme);
  
  // 4. state의 값도 변경 + local 저장 값도 변경
  const setMode = (mode: ColorTheme) => {
    mode === lightTheme
      ? window.localStorage.setItem('theme', 'light')
      : window.localStorage.setItem('theme', 'dark');
    setColorTheme(mode);
  };
  // 3. 사용자가 toggleColorTheme을 하면 setMode를 통해 기존의 colorTheme과 반대 값을 저장한다.
  const toggleColorTheme = () => {
    colorTheme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
  };
  // 2. 마운트 되면 localStorage에 'theme'이 있는지 찾는다.
  // - 새로고침시 다크모드/라이트모드 바로 적용
  // - 페이지 로드가 처음이면 이 과정은 무시된다
  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    if (localTheme !== null) { // localTheme이 존재한다면
      if (localTheme === 'dark') {
        setColorTheme(darkTheme);
      } else {
        setColorTheme(lightTheme);
      }
    }
  }, []);
  return { colorTheme, toggleColorTheme };
};props를 계속 넘겨주는 방식을 피하기 위해서 Context API를 사용합니다.
페이지를 이동해도 테마를 유지할 수 있도록 ThemeContext.Provider를 통해서 하위 컴포넌트들이 Context를 통해 테마를 접근할 수 있도록합니다.
Next.js에서는 최상단이
_app이기 때문에 여기서 설정을 하게 됩니다.
// pages/_app.tsx
import React, { createContext } from 'react';
import type { AppProps } from 'next/app';
import { Global } from '@emotion/react';
import { lightTheme, darkTheme, ColorTheme } from '../styles/theme';
// createContext 타입지정
interface ContextProps { 
  colorTheme: ColorTheme;
  toggleColorTheme: () => void;
}
// Context 생성
export const ThemeContext = createContext<ContextProps>({
  colorTheme: lightTheme, // 초기 값으로 lightTheme를 넣어줍니다.
  toggleColorTheme: () => { // light || dark mode를 토글합니다.
    return null
  },
})
function MyApp({ Component, pageProps }: AppProps) {
  // ❗️useDarkMode hook을 통해 theme과 toggleTheme return;
 const { theme, toggleTheme } = useDarkMode();
  
  return (
    // Provider은 context의 변화를 알리는 역할을 합니다.
    // toggleTheme를 통해 theme이 변경되면 하위 컴포넌트들은 모두 리렌더링됩니다.
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
        <Component {...pageProps} />
    </ThemeContext.Provider>
  )
}
export default MyApp
4️⃣ 다크모드 사용 및 토글 버튼
- 
useDarkMode를 통해 생성되고 ThemeContext로 전파가 된 	~colorTheme, toggleColorTheme를 useContext를 사용해서 구독합니다.
 
- 
버튼을 클릭하면 toggleColorTheme을 작동합니다.
 
- 
가져온 colorTheme를 통해 css를 적용합니다.
 
// components/Header/HeaderBtns/DarkModeToggle/index.tsx
import React, { ReactElement, useContext } from 'react';
import { ThemeContext } from '../../../../pages/_app';
import styled from '@emotion/styled';
import { lightTheme, ColorTheme } from '../../../../styles/theme';
interface ToggleProps {
  colorTheme: ColorTheme;
}
const DarkModeToggle = () => {
  // 1. useContext를 통해서 colorTheme, toggleColorTheme를 구독한다
  const { colorTheme, toggleColorTheme } = useContext(ThemeContext);
  return (
    // 2. 버튼을 클릭하면 toggleColorTheme을 작동한다
    <ToggleButton onClick={toggleColorTheme} colorTheme={colorTheme}>
      {colorTheme === lightTheme ? '다크 모드' : '라이트 모드'}
    </ToggleButton>
  );
}
// 3. colorTheme을 prop으로 가져와 css를 적용한다.
const ToggleButton = styled('button')<ToggleProps>`
  display: flex;
  color: ${({ colorTheme }) => colorTheme.MAIN};
  cursor: pointer;
  background: ${({ colorTheme }) => colorTheme.BACKGROUND};
  box-shadow: 3px 3px 10px rgb(0 0 0 / 20%);
  &:hover {
    filter: brightness(${({ colorTheme }) => (colorTheme === lightTheme ? '0.9' : '1.13')});
  }
`;
export default DarkModeToggle;
                
                    
        
    
    
    
    
    
                
                
                
                
                    
                        
                            
                            
                            Author And Source
                            
                            이 문제에 관하여([React.js] 다크모드 (Emotion.js + Next.js + TypeScript)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
                                
                                https://velog.io/@minbr0ther/React.js-다크모드-Emotion.js-Next.js-TypeScript
                            
                            
                            
                                저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
                            
                            
                                
                                
                                
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (Collection and Share based on the CC Protocol.)
                            
                            
                        
                    
                
                
                
            
useDarkMode를 통해 생성되고 ThemeContext로 전파가 된 	~colorTheme, toggleColorTheme를 useContext를 사용해서 구독합니다.
버튼을 클릭하면 toggleColorTheme을 작동합니다.
가져온 colorTheme를 통해 css를 적용합니다.
// components/Header/HeaderBtns/DarkModeToggle/index.tsx
import React, { ReactElement, useContext } from 'react';
import { ThemeContext } from '../../../../pages/_app';
import styled from '@emotion/styled';
import { lightTheme, ColorTheme } from '../../../../styles/theme';
interface ToggleProps {
  colorTheme: ColorTheme;
}
const DarkModeToggle = () => {
  // 1. useContext를 통해서 colorTheme, toggleColorTheme를 구독한다
  const { colorTheme, toggleColorTheme } = useContext(ThemeContext);
  return (
    // 2. 버튼을 클릭하면 toggleColorTheme을 작동한다
    <ToggleButton onClick={toggleColorTheme} colorTheme={colorTheme}>
      {colorTheme === lightTheme ? '다크 모드' : '라이트 모드'}
    </ToggleButton>
  );
}
// 3. colorTheme을 prop으로 가져와 css를 적용한다.
const ToggleButton = styled('button')<ToggleProps>`
  display: flex;
  color: ${({ colorTheme }) => colorTheme.MAIN};
  cursor: pointer;
  background: ${({ colorTheme }) => colorTheme.BACKGROUND};
  box-shadow: 3px 3px 10px rgb(0 0 0 / 20%);
  &:hover {
    filter: brightness(${({ colorTheme }) => (colorTheme === lightTheme ? '0.9' : '1.13')});
  }
`;
export default DarkModeToggle;Author And Source
이 문제에 관하여([React.js] 다크모드 (Emotion.js + Next.js + TypeScript)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@minbr0ther/React.js-다크모드-Emotion.js-Next.js-TypeScript저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
                                
                                
                                
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (Collection and Share based on the CC Protocol.)