테마스위처 (Theme Swicher with Context API)

henry·2024년 11월 3일

Theme toggle switch


App.tsx에서 테마 상태를 관리

  • App 컴포넌트에서 themeName이라는 상태를 useState로 정의
  • 이 상태는 현재 적용 중인 테마 정보를 저장(light 또는 dark)
  • 테마 정보를 ThemeProviderThemeSwitcher 컴포넌트로 전달

App.tsx

function App() {
   const [themeName, setThemeName] = useState<ThemeName>('light');

   return (
      <ThemeProvider theme={getTheme(themeName)}>
         <GlobalStyle themeName={themeName} />
         <ThemeSwitcher themeName={themeName} setThemeName={setThemeName} />
         <Layout>
            <Home />
         </Layout>
      </ThemeProvider>
   );
}

테마 토글 기능 구현

  • ThemeSwitcher 컴포넌트는 전달받은 themeName을 기준으로 테마 변경

ThemeSwitcher.tsx

import { ThemeName } from '../../style/theme';

interface Props {
   themeName: ThemeName;
   setThemeName: (themeName: ThemeName) => void;
}

const ThemeSwitcher = ({ themeName, setThemeName }: Props) => {
   const toggleTheme = () => {
      setThemeName(themeName === 'light' ? 'dark' : 'light');
   };

   return <button onClick={toggleTheme}>{themeName}</button>;
};

export default ThemeSwitcher;
  • toggleTheme 함수가 버튼 이벤트로 호출이 되면, themeName 값을 변경

theme.ts

export type ThemeName = 'light' | 'dark';
type ColorKey = 'primary' | 'background' | 'secondary' | 'third';

interface Theme {
   name: ThemeName;
   color: Record<ColorKey, string>;
}

export const light: Theme = {
   name: 'light',
   color: {
      primary: 'brown',
      background: 'lightgray',
      secondary: 'blue',
      third: 'green',
   },
};

export const dark: Theme = {
   name: 'dark',
   color: {
      primary: 'coral',
      background: 'midnightblue',
      secondary: 'blue',
      third: 'green',
   },
};

export const getTheme = (themeName: ThemeName): Theme => {
   switch (themeName) {
      case 'light':
         return light;
      case 'dark':
         return dark;
   }
};

로직

  • 초기 상태
    themeName이 'light'로 설정

  • 테마 설정
    ThemeProvider가 themeName에 따라 테마 적용

  • 테마 변경 준비
    ThemeSwitcher가 themeName과 setThemeName을 통해 테마 토글 준비

  • 버튼 클릭
    버튼 클릭 시 toggleTheme 함수가 실행되어 themeName 변경

  • 리렌더링
    themeName이 변경되어 App이 리렌더링되고 새로운 테마가 적용



Context API

중첩된 컴포넌트로 일일이 props를 전달하지 않고도 컴포넌트 트리 전반에서 접근할 수 있도록 테마 관리의 Context API 리팩토링

themeContext.tsx

어플리케이션에서 테마를 변경하고 유지


소스 코드

import { createContext, ReactNode, useEffect, useState } from 'react';
import { ThemeName, getTheme } from '../style/theme';
import { ThemeProvider } from 'styled-components';
import { GlobalStyle } from '../style/global';

const DEFAULT_THEME_NAME = 'light';
const THEME_LOCAL_STORAGE_KEY = 'book_store_theme';

interface State {
   themeName: ThemeName;
   toggleTheme: () => void;
}

export const state = {
   themeName: DEFAULT_THEME_NAME as ThemeName,
   toggleTheme: () => {},
};

export const ThemeContext = createContext<State>(state);

export const BookStoreThemeProvider = ({ children }: { children: ReactNode }) => {
   const [themeName, setThemeName] = useState<ThemeName>('dark');

   const toggleTheme = () => {
      setThemeName(themeName === 'light' ? 'dark' : 'light');
      localStorage.setItem(THEME_LOCAL_STORAGE_KEY, themeName === 'light' ? 'dark' : 'light');
   };

   useEffect(() => {
      const savedThemeName = localStorage.getItem(THEME_LOCAL_STORAGE_KEY) as ThemeName;
      setThemeName(savedThemeName || DEFAULT_THEME_NAME);
   }, []);

   return (
      <ThemeContext.Provider value={{ themeName, toggleTheme }}>
         <ThemeProvider theme={getTheme(themeName)}>
            <GlobalStyle themeName={themeName} />
            {children}
         </ThemeProvider>
      </ThemeContext.Provider>
   );
};

1. 기본값과 상수 설정

const DEFAULT_THEME_NAME = 'light';
const THEME_LOCAL_STORAGE_KEY = 'book_store_theme';
  • DEFAULT_THEME_NAME
    기본 테마 이름을 'light'로 설정
  • THEME_LOCAL_STORAGE_KEY
    테마 정보를 브라우저에 저장할 때 사용할 키(key)
  • 브라우저가 데이터를 기억하는 로컬 스토리지로 저장된 테마 정보를 사용

2. 상태 인터페이스 정의

interface State {
   themeName: ThemeName;
   toggleTheme: () => void;
}
  • State
    어플리케이션의 테마 상태가 어떤 구조로 되어야 하는지 설명
  • themeName
    현재 테마 이름 저장 ('light' 또는 'dark')
  • toggleTheme
    테마 변경 함수

interface State는 테마에 대한를 명확하게 정의해주는 역할
코드에서 themeName과 toggleTheme이 어떤 타입이어야 하는지를 지정


3. 초기 상태 값 설정

export const state = {
   themeName: DEFAULT_THEME_NAME as ThemeName,
   toggleTheme: () => {},
};
  • state
    기본 테마 상태
  • themeName
    DEFAULT_THEME_NAME으로 설정
  • toggleTheme
    비어 있는 함수로 초기화합니다.

4. 컨텍스트 생성

export const ThemeContext = createContext<State>(state);
  • ThemeContext
    React에서 제공하는 컨텍스트(Context) 기능

    • createContext는 React에서 상태를 공유할 수 있는 Context 객체를 생성하는 함수
    • 컴포넌트 간에 특정한 값을 손쉽게 공유할 수 있게 하는 Context 객체
  • 테마 상태를 컴포넌트 트리 전체에 전달하는 역할

  • createContext<State>(state)
    state 객체를 초기값으로 사용하여 ThemeContext라는 컨텍스트

  • 컴포넌트들 사이에서 테마 상태를 공유하는데 사용


5. 테마 제공자 컴포넌트

export const BookStoreThemeProvider = ({ children }: { children: ReactNode }) => {
   const [themeName, setThemeName] = useState<ThemeName>('dark');
  • BookStoreThemeProvider
    어플리케이션의 최상위에서 테마 정보를 관리하고 제공하는 컴포넌트

  • children
    React 컴포넌트에서 children은 이 컴포넌트 안에 위치한 다른 모든 컴포넌트를 의미

  • themeName, setThemeName
    useState를 통해 정의, 테마 이름을 변경할 수 있는 상태

    children이라는 인자를 통해 감싸고 있는 하위 컴포넌트들을 받아서 렌더링
    children에는 BookStoreThemeProvider로 감싸진 모든 자식 컴포넌트들이 들어감

  function App() {
     return (
        <BookStoreThemeProvider>
           <ThemeSwitcher />
           <Layout>
              <Home />
           </Layout>
        </BookStoreThemeProvider>
     );
  }

6. 토글 함수 (테마 변경 함수)

const toggleTheme = () => {
   setThemeName(themeName === 'light' ? 'dark' : 'light');
   localStorage.setItem(THEME_LOCAL_STORAGE_KEY, themeName === 'light' ? 'dark' : 'light');
};
  • toggleTheme는 테마를 변경하는 함수
  • 현재 테마가 'light'면 'dark'로, 'dark'면 'light'로 변경
  • 변경된 테마 이름은 localStorage에 저장
  • 페이지를 새로고침해도 사용자가 마지막으로 선택한 테마 유지

7. 페이지 로드시 로컬 스토리지에서 테마 불러오기

useEffect(() => {
   const savedThemeName = localStorage.getItem(THEME_LOCAL_STORAGE_KEY) as ThemeName;
   setThemeName(savedThemeName || DEFAULT_THEME_NAME);
}, []);
  • useEffect는 컴포넌트가 처음 나타날 때 로컬 스토리지에서 테마 정보를 불러옴
  • 저장된 테마가 있으면 그 테마를 사용
  • 없으면 DEFAULT_THEME_NAME을 사용

8. 컨텍스트와 테마 제공

return (
   <ThemeContext.Provider value={{ themeName, toggleTheme }}>
      <ThemeProvider theme={getTheme(themeName)}>
         <GlobalStyle themeName={themeName} />
         {children}
      </ThemeProvider>
   </ThemeContext.Provider>
);
  • ThemeContext.Provider
    하위 컴포넌트들이 themeName과 toggleTheme를 사용할 수 있게 함
  • ThemeProvider, GlobalStyle
    현재 테마의 스타일을 어플리케이션에 적용
  • getTheme(themeName)
    themeName에 맞는 색상과 스타일을 적용

0개의 댓글