๐ŸŽ€ Wedding-card - ๋ชจ๋“œ์Šค์œ„์น˜

Graceยท2021๋…„ 10์›” 21์ผ
0

wedding-card

๋ชฉ๋ก ๋ณด๊ธฐ
7/7
post-thumbnail

styled-components์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๋” ์•Œ ์ˆ˜ ์žˆ์—ˆ๋˜
๋‹คํฌ ๋ชจ๋“œ์Šค์œ„์น˜ ์ž‘์—…ํŒŒํŠธ.
๋‹น์‹œ์— ๊ธ‰ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š๋ผ ThemeProvider๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ,
์‚ฌ์šฉํ•˜๊ธฐ ๊ธ‰๊ธ‰ํ•ด์„œ ์ œ๋Œ€๋กœ ์ •๋ฆฌ๊ฐ€ ์•ˆ๋œ ๊ฒƒ ๊ฐ™์•„์„œ
๋‹ค์‹œ ๊ณต๋ถ€ํ•˜๋ฉฐ ๋ฆฌํŒฉํ† ๋งํ•˜๊ณ  ์ •๋ฆฌํ•˜๊ธฐ!

๋‹คํฌ๋ชจ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ž์„ธํ•œ ๋ฐฉ๋ฒ•๋ณด๋‹จ ์„ค์ •๋˜๋Š” ๊ณผ์ •์— ์ง‘์ค‘ํ•ด ์ •๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค


๐ŸŒ™ Dark-mode Switch

์ตœ๊ทผ ์›น์ด๋‚˜ ์•ฑ์„ ๋ณด๋ฉด ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ๋ˆˆ์˜ ํ”ผ๋กœ๋„๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ๋„๋ก
๋‹คํฌ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒƒ์„ ๋งŽ์ด ๋ณด์•˜๋‹ค.
๊ทธ๋ž˜์„œ ๋‚˜๋„ ํ”„๋กœ์ ํŠธ์— ๋งˆ์ง€๋ง‰์— ์ถ”๊ฐ€ ํ•ด๋ณด๊ฒŒ ๋˜์—ˆ๋‹ค.
์•„๋ฌด๋ž˜๋„ ์Šคํƒ€์ผ๋ง์ ์œผ๋กœ ์กฐ์ ˆํ•ด์•ผ ํ•˜๋Š”๊ฒŒ ๋งŽ์•„์„œ ๋งˆ์ง€๋ง‰์— ํ•˜๋Š”๊ฒŒ ๋‚˜์•˜๋˜ ๊ฒƒ ๊ฐ™๋‹ค :)

โš™๏ธ ์ž‘๋™๊ตฌ์กฐ

์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๊ฒ ์ง€๋งŒ, ์Šคํƒ€์ผ๋ง์„ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ
styled-components์˜ ThemeProvider๋ฅผ ํ†ตํ•ด ์ „์—ญ์œผ๋กœ ์Šคํƒ€์ผ๋ง์ด
๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ๋„๋ก ๊ด€๋ฆฌํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

useThemeMode๋ผ๋Š” ์ปค์Šคํ…€ํ›…์„ ๋งŒ๋“ค์–ด์„œ localStorage์— ๊ฐ’์„ ์ €์žฅํ•˜์—ฌ
์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜๋”๋ผ๋„ ์„ค์ •ํ•œ ๋ชจ๋“œ๊ฐ€ ์œ ์ง€๋  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ–ˆ๋‹ค.

๊ทธ๋Ÿฐ ๋‹ค์Œ,
App.tsx์—์„œ ์ปค์Šคํ…€ํ›…์„ ๋ถˆ๋Ÿฌ์™€์„œ ๋ชจ๋“œ์— ๊ด€ํ•œ ๊ฐ’์€
ThemeProvider์— ์ „๋‹ฌํ•˜๊ณ ,
๋ชจ๋“œ๋ฅผ ๋ณ€๊ฒฝ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋Š” ๋ชจ๋“œ์Šค์œ„์น˜ ๋ฒ„ํŠผ์— ์ „๋‹ฌํ•ด์„œ
์Šคํƒ€์ผ๋ง์ด ๋ณ€๊ฒฝ๋˜๊ฒŒ ํ–ˆ๋‹ค.

๐Ÿ•น custom hook - useThemeMode

ํ…Œ๋งˆ ๋ชจ๋“œ ์„ค์ •์„ ์œ„ํ•œ useThemeMode๋ผ๋Š” ํ›…์„ ๋งŒ๋“ค์—ˆ๋‹ค.
์ƒํƒœ๊ฐ’์ธ themeMode์— booleanํ˜•์œผ๋กœ
true์ผ ๋• ๋‹คํฌ๋ชจ๋“œ, false์ผ ๊ฒฝ์šฐ์—” ๋ผ์ดํŠธ๋ชจ๋“œ๋กœ ์„ค์ •๋˜๊ฒŒ ํ–ˆ๋‹ค.

const useThemeMode = () => {
  // ๋ธŒ๋ผ์šฐ์ €์˜ ํ…Œ๋งˆ์ •๋ณด ํ™•์ธ
  const isBrowserDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme : dark').matches;
  
  // ๋ธŒ๋ผ์šฐ์ €์˜ ํ…Œ๋งˆ์ •๋ณด๋ฅผ ํ† ๋Œ€๋กœ ์ดˆ๊ธฐ ์ƒํƒœ๊ฐ’ ์„ค์ •
  let initialTheme = isBrowserDarkMode;
  const [themeMode, setThemeMode] = useState<boolean>(initialTheme);
  
  // ์‚ฌ์šฉ์ž ์ง€์ • ํ…Œ๋งˆ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ ํ›„ ์žˆ์œผ๋ฉด ํ•ด๋‹น ํ…Œ๋งˆ๋กœ ์„ค์ •, ์—†๋‹ค๋ฉด ๋ธŒ๋ผ์šฐ์ € ํ…Œ๋งˆ๋กœ ์„ค์ •
  const localSettingTheme = Boolean(localStorage.getItem("themeMode"));
  if(localSettingTheme) {
    initialTheme = localSettingTheme;
  }
  
  const setMode = (mode : boolean) => {
    localStorage.setItem("themeMode", JSON.stringify(mode));
    setThemeMode(mode);
  }
  
  // themeMode ๋ณ€๊ฒฝ ์‹œ๋งˆ๋‹ค localStorage์— ์„ค์ •๋˜๋ฉด์„œ ํ…Œ๋งˆ ๋ณ€๊ฒฝ๋˜๊ฒŒ ํ•˜๋Š” ํ•จ์ˆ˜ ์ƒ์„ฑ
  const switchThemeMode = () => {
    setMode(!themeMode);
  }
  
  return {
    themeMode,
    switchThemeMode
  }
  
  export default useThemeMode;
    

์—ฌ๊ธฐ์„œ ๋ช‡๊ฐ€์ง€ ํ™•์ธํ•ด ๋ณผ ์ฝ”๋“œ๋Š”,
matchMedia() ๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €์˜ ํ…Œ๋งˆ๋ชจ๋“œ๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๐Ÿ“ window.matchMedia()๋ž€?
์ฃผ์–ด์ง„ ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด์˜ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” MediaQueryList ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
์ด๋ฒˆ ์ž‘์—…์—์„œ ์‚ฌ์šฉํ•œ prefers-color-scheme : dark๋Š” ์‚ฌ์šฉ์ž์˜ ์‹œ์Šคํ…œ์ด ๋‹คํฌ ํ…Œ๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€๋ฅผ ํƒ์ง€ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

์ดํ›„, Boolean() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์„œ
์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ ํ…Œ๋งˆ๊ฐ€ ์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ์ž‘์—…์„ ๊ฑฐ์นœ ํ›„
์žˆ๋‹ค๋ฉด ํ•ด๋‹น ํ…Œ๋งˆ๋กœ, ์—†๋‹ค๋ฉด ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ํ…Œ๋งˆ๋กœ ์„ค์ •ํ•˜๊ฒŒ ํ–ˆ๋‹ค.

๐Ÿ“ Boolean ํ•จ์ˆ˜๋ž€?

  • ๋‹ค๋ฅธ ์ž๋ฃŒํ˜•์„ booleanํ˜•์œผ๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜
    • false : 0, '', null, undefined, NaN
    • true : false๋ฅผ ์ œ์™ธํ•œ ๊ฒฝ์šฐ, ์ฐธ์ธ ํ‘œํ˜„์‹

localStorage์— themeMode๋ฅผ ์„ค์ •ํ•  ๋•Œ๋Š”,
key๊ฐ’๊ณผ value๊ฐ’์ด ๋ชจ๋‘ string์ด์–ด์•ผ ํ•˜๋Š”๋ฐ
๋‚˜๋Š” themeMode๊ฐ’์„ booleanํ˜•์œผ๋กœ ์„ค์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—
JSON.stringify๋กœ stringํ™” ์ž‘์—…์„ ๊ฑฐ์ณ์ฃผ์—ˆ๋‹ค.

ThemeProvider

ThemeProvider๋ž€ react์˜ context API๋ฅผ ์‚ฌ์šฉํ•ด์„œ
์ „์—ญ์ ์œผ๋กœ ์Šคํƒ€์ผ๋ง์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก styled-components์—์„œ
์ œ๊ณตํ•˜๋Š” ์ „์—ญ์Šคํƒ€์ผ๋ง ๊ธฐ๋Šฅ์ด๋‹ค.

๋ฐ˜๋“œ์‹œ theme ์†์„ฑ์„ ๊ฐ–๋Š” ์ปดํฌ๋„ŒํŠธ๋กœ,
theme ์†์„ฑ์˜ ๊ฐ’์œผ๋กœ ์„ค์ •๋œ ๊ฐ์ฒด๋ฅผ
์ผ์ผ์ด ๋„˜๊ฒจ์ค„ ํ•„์š” ์—†์ด ํ•œ๋ฒˆ์— ๋ชจ๋“  ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ props๋กœ ์ „๋‹ฌํ•œ๋‹ค.

์œ„์—์„œ ์ž‘์„ฑํ•œ ์ปค์Šคํ…€ ํ›…๊ณผ ํ•จ๊ป˜ ThemeProvider ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž.

...
import { ThemeProvider } from "styled-components";
import { darkTheme, lightTheme } from "./globals/theme";
import useThemeMode from "./hooks/useThemeMode";
import SwitchButton from "./components/SwitchButton";

function App() {
  const {themeMode, switchThemeMode} = useThemeMode();
  
  return (
    <ThemeProvider theme={themeMode ? darkTheme : lightTheme}>
    	<SwitchButton changeTheme={switchThemeMode} isDark={themeMode} />
    </ThemeProvider>
  );
}

export default App;

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ThemeProvider์˜ theme์„ ํ†ตํ•ด
์ปค์Šคํ…€ ํ›…์— ์ ‘๊ทผํ•ด themeMode๋ฅผ ๊ฐ์ง€ํ•˜๋ฉฐ
์Šค์œ„์น˜ ๋ฒ„ํŠผ์ด ํด๋ฆญ ๋  ๊ฒฝ์šฐ์—” ํ•จ์ˆ˜๊ฐ€ ๋ฐœ๋™๋˜๋ฉด์„œ
๋ชจ๋“œ๊ฐ€ ๋ณ€๊ฒฝ์ด ๋˜๊ณ ,
ThemeProvider๊ฐ€ ๊ฐ์‹ธ๊ณ  ์žˆ๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์Šคํƒ€์ผ๋ง์ด
๋‚ด๊ฐ€ ๊ตฌ๋ถ„ํ•ด๋‘” ๋Œ€๋กœ ๋ชจ๋‘ ๋ณ€๊ฒฝ๋œ๋‹ค.


๋‚˜๋ฆ„ ์ดํ•ดํ•œ๋Œ€๋กœ ์ •๋ฆฌํ–ˆ๋Š”๋ฐ ํ˜น์‹œ๋‚˜ ํ‹€๋ฆฐ ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์„ธ์š”!
์ฆ๊ฒ๊ฒŒ ๋‹ค์‹œ ๊ณต๋ถ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค ๐Ÿค—

๐Ÿ“Œ ์ฐธ๊ณ ํ•˜์˜€์Šต๋‹ˆ๋‹ค :)

profile
์‰ฝ๊ฒŒ ์‚ฌ๋Š”๊ฑด ์žฌ๋ฏธ๊ฐ€ ์—†๋”๊ตฐ์š”, ์ƒˆ๋กœ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค๐Ÿค“

0๊ฐœ์˜ ๋Œ“๊ธ€