ThemeProvider와 ThemeSwitcher 컴포넌트로 전달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.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;
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이 리렌더링되고 새로운 테마가 적용
중첩된 컴포넌트로 일일이 props를 전달하지 않고도 컴포넌트 트리 전반에서 접근할 수 있도록 테마 관리의 Context API 리팩토링
소스 코드
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>
);
};
const DEFAULT_THEME_NAME = 'light';
const THEME_LOCAL_STORAGE_KEY = 'book_store_theme';
DEFAULT_THEME_NAMETHEME_LOCAL_STORAGE_KEYinterface State {
themeName: ThemeName;
toggleTheme: () => void;
}
StatethemeNametoggleThemeinterface State는 테마에 대한를 명확하게 정의해주는 역할
코드에서 themeName과 toggleTheme이 어떤 타입이어야 하는지를 지정
export const state = {
themeName: DEFAULT_THEME_NAME as ThemeName,
toggleTheme: () => {},
};
statethemeNametoggleThemeexport const ThemeContext = createContext<State>(state);
ThemeContext
React에서 제공하는 컨텍스트(Context) 기능
createContext는 React에서 상태를 공유할 수 있는 Context 객체를 생성하는 함수테마 상태를 컴포넌트 트리 전체에 전달하는 역할
createContext<State>(state)
state 객체를 초기값으로 사용하여 ThemeContext라는 컨텍스트
컴포넌트들 사이에서 테마 상태를 공유하는데 사용
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>
);
}
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>
);
ThemeContext.ProviderThemeProvider, GlobalStylegetTheme(themeName)