๐ŸŒ— Styled Components๋กœ ๋ผ์ดํŠธ/๋‹คํฌ ๋ชจ๋“œ ์ „ํ™˜ํ•˜๊ธฐ

์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ๋‹คํฌ ๋ชจ๋“œ์™€ ๋ผ์ดํŠธ ๋ชจ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•œ UX ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ด๋‹ค.

styled-components์™€ ThemeProvider๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ „์—ญ ํ…Œ๋งˆ ์ „ํ™˜์„ ๋งค์šฐ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.


โœ… 1. ํ…Œ๋งˆ ๊ฐ์ฒด ์ •์˜ํ•˜๊ธฐ

// themes.js
export const lightTheme = {
  textColor: "#111",
  backgroundColor: "whitesmoke",
};

export const darkTheme = {
  textColor: "whitesmoke",
  backgroundColor: "#111",
};

โœ… 2. App์—์„œ ํ…Œ๋งˆ ์ƒํƒœ ๊ด€๋ฆฌ

์—ฌ๋Ÿฌ๊ฐœ์˜ property๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ๋‘๊ฐœ (๋‹คํฌ/๋ผ์ดํŠธ) ์„ค์ •ํ•จ์œผ๋กœ์จ ๊ฐ„ํŽธํ•˜๊ฒŒ theme์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. 

 

// App.js
import React, { useState } from "react";
import styled, { ThemeProvider } from "styled-components";
import { lightTheme, darkTheme } from "./themes";

const Title = styled.h1`
  color: ${(props) => props.theme.textColor};
`;

const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
  gap: 20px;
  height: 100vh;
  width: 100vw;
  justify-content: center;
  align-items: center;
  background-color: ${(props) => props.theme.backgroundColor};
  transition: all 0.3s ease-in-out;
`;

const ToggleButton = styled.button`
  padding: 10px 20px;
  font-weight: bold;
  border: none;
  cursor: pointer;
  background-color: ${(props) => props.theme.textColor};
  color: ${(props) => props.theme.backgroundColor};
  border-radius: 8px;
`;

function App() {
  const [isDark, setIsDark] = useState(true);
  const toggleTheme = () => setIsDark((prev) => !prev);

  return (
    <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
      <Wrapper>
        <Title>{isDark ? "Dark Mode" : "Light Mode"}</Title>
        <ToggleButton onClick={toggleTheme}>
          Toggle Theme
        </ToggleButton>
      </Wrapper>
    </ThemeProvider>
  );
}

export default App;

โœ… ๊ฒฐ๊ณผ ํ™”๋ฉด

  • ๊ธฐ๋ณธ์€ ๋‹คํฌ ๋ชจ๋“œ (isDark = true)
  • ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ผ์ดํŠธ ๋ชจ๋“œ๋กœ ์ „ํ™˜๋จ
  • ThemeProvider๋Š” ์ƒํƒœ์— ๋”ฐ๋ผ lightTheme ๋˜๋Š” darkTheme์„ ์ „๋‹ฌ


๐Ÿ”ง ThemeProvider ์ž‘๋™ ์›๋ฆฌ

โœ… 1. React Context๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค

styled-components์˜ ThemeProvider๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ React Context API๋ฅผ ์‚ฌ์šฉํ•ด ํ…Œ๋งˆ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

 
<ThemeProvider theme={currentTheme}> 
	<App /> 
</ThemeProvider>
 

์ด๋ ‡๊ฒŒ ๊ฐ์‹ธ๋ฉด ThemeProvider๋Š” theme ๊ฐ์ฒด๋ฅผ Context๋กœ ์ €์žฅํ•˜๊ณ , ํ•˜์œ„์˜ ๋ชจ๋“  styled component๋Š” ๊ทธ ๊ฐ’์„ props.theme์„ ํ†ตํ•ด ์ž๋™์œผ๋กœ ์ฃผ์ž…๋ฐ›๋Š”๋‹ค.


โœ… 2. styled-components๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ThemeContext๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค

styled-components๋Š” ThemeContext๋ผ๋Š” ๋‚ด๋ถ€ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , ThemeProvider๋กœ ์ „๋‹ฌ๋ฐ›์€ theme์„ ์—ฌ๊ธฐ์— ์ €์žฅํ•œ๋‹ค.

// (๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ๋Œ€๋žต ์ด๋Ÿฐ ํ˜•ํƒœ) 
const ThemeContext = React.createContext(defaultTheme); 
export const ThemeProvider = ({ theme, children }) => { 
	return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>; 
};
 

โœ… 3. styled component๋Š” theme context์— ์ ‘๊ทผํ•˜์—ฌ props.theme์œผ๋กœ ๋„ฃ์–ด์ค€๋‹ค

๋ชจ๋“  styled ์ปดํฌ๋„ŒํŠธ๋Š” ์ƒ์„ฑ๋  ๋•Œ ThemeContext๋ฅผ ์ž๋™์œผ๋กœ ๊ตฌ๋…ํ•˜๊ณ , ๋ Œ๋”๋ง ์‹œ theme ๊ฐ’์„ props.theme์œผ๋กœ ๋„ฃ์–ด์ค€๋‹ค.

const Title = styled.h1` 
	color: ${(props) => props.theme.textColor}; 
`;

โ†’ ์—ฌ๊ธฐ์„œ props.theme.textColor๋Š” ThemeProvider๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋œ ๊ฐ’์ด๋‹ค.
โ†’ ๋งŒ์•ฝ ThemeProvider๊ฐ€ ์—†์œผ๋ฉด props.theme๋Š” undefined๋‹ค.

 

โœ… ์ผ๋‹จ styled-component ๋กœ ํ•œ๋ฒˆ ๋งŒ๋“ค์–ด๋ณด๊ธด ํ–ˆ์ง€๋งŒ.. 

  • localStorage์— ์‚ฌ์šฉ์ž์˜ ํ…Œ๋งˆ ์„ ํ˜ธ ์ €์žฅํ•˜์—ฌ ์œ ์ง€ํ•˜๊ธฐ
  • ์‹œ์Šคํ…œ ์„ค์ •์— ๋”ฐ๋ฅธ ๋‹คํฌ๋ชจ๋“œ ์ž๋™ ์ ์šฉ (window.matchMedia)

์‹ค์œ ์ €๊ฐ€ ์žˆ๋Š” ์„œ๋น„์Šค์—์„œ๋Š” ์œ„์˜ ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š”๊ฒŒ ์ข‹์„๋“ฏ. 


React + Styled-components๋กœ ๊ตฌํ˜„ํ•œ ํ…Œ๋งˆ ์‹œ์Šคํ…œ์€ ํ™•์žฅ์„ฑ๋„ ์ข‹๊ณ , ์œ ์ง€๋ณด์ˆ˜๋„ ์‰ฝ๋‹ค.
๋‹คํฌ๋ชจ๋“œ ๋ฒ„ํŠผ์„ ๋„ฃ๊ณ  ์‹ถ๋‹ค๋ฉด ์ด ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณด์ž!

 

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€