thumbnail
Gatsby λΈ”λ‘œκ·Έμ— darkmode κ΅¬ν˜„
λΈ”λ‘œκ·Έ
2023.09.08.

Dark Mode

λͺ‡ λ…„ μ „λΆ€ν„° λ‹€μ–‘ν•œ μ•±, μ›Ή 그리고 ν•˜λ“œμ›¨μ–΄μ—μ„œ 닀크λͺ¨λ“œλ₯Ό μ§€μ›ν•˜κΈ° μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€. ν˜„μž¬λŠ” 닀크λͺ¨λ“œκ°€ μ•ˆλ˜λŠ” νŽ˜μ΄μ§€κ°€ 잘 보이지 μ•Šμ„ μ •λ„λ‘œ ν”ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ 저도 λΈ”λ‘œκ·Έμ— 닀크λͺ¨λ“œλ₯Ό μ μš©ν•΄λ³΄κΈ°λ‘œ μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.

μž‘μ—…μ„ μ‹œμž‘ν•˜κΈ°μ „μ— λ‹€μ–‘ν•œ λ‹€λ₯Έ κ°œλ°œμžλ“€μ˜ 개발 λΈ”λ‘œκ·Έλ₯Ό μ‚΄νŽ΄λ³΄λŠ”λ° 같은 Gatsbyλ₯Ό μ‚¬μš©ν•˜λ”λΌλ„ 닀크λͺ¨λ“œλ₯Ό μ μš©ν•œ λ°©μ‹μ—λŠ” 차이가 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

닀크λͺ¨λ“œλ₯Ό κ΅¬ν˜„ν•˜λ©΄μ„œ 정말 λ§Žμ€ 글듀을 μ°Έκ³ ν–ˆμ§€λ§Œ, 였히렀 λ„ˆλ¬΄ λ§Žμ€ 글을 읽은 탓인지 μ°Έκ³ ν•œ λΈ”λ‘œκ·Έλ“€μ„ λ§ˆν¬μ—… 해두지 μ•Šμ•„μ„œ 막상 μ΄λ ‡κ²Œ 포슀트둜 μ •λ¦¬ν•΄λ‘λ €ν•˜λ‹ˆ 어렀움이 μžˆμŠ΅λ‹ˆλ‹€.

μ €λŠ” μ–΄λ–»κ²Œ 닀크λͺ¨λ“œλ₯Ό κ΅¬ν˜„μ„ ν–ˆλŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

1. ThemeToggle React μ»΄ν¬λ„ŒνŠΈ

import React from 'react'

type ThemeToggleProps = {
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

const ThemeToggle: React.FC<ThemeToggleProps> = ({ theme, toggleTheme }) => {
  const themeClassName = theme === 'light' ? 'light-mode' : 'dark-mode'

  return (
    <button
      onClick={toggleTheme}
      className={`themeToggle ${themeClassName}`}
    >
      {theme === 'light' ? '닀크λͺ¨λ“œ' : '라이트λͺ¨λ“œ'}
    </button>
  )
}

export default ThemeToggle

라이트 λͺ¨λ“œμ™€ 닀크 λͺ¨λ“œ κ°„μ˜ ν…Œλ§ˆ μ „ν™˜μ„ μ²˜λ¦¬ν•˜λŠ” React μ»΄ν¬λ„ŒνŠΈλ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. μ½”λ“œλ₯Ό κ°„λ‹¨ν•˜κ²Œ 리뷰해보면,

  1. type ThemeToggleProps = {} Typescriptλ₯Ό μ‚¬μš©ν•˜μ—¬ ThemeToggle μ»΄ν¬λ„ŒνŠΈμ˜ prop νƒ€μž…μ„ μ •μ˜ ν–ˆμŠ΅λ‹ˆλ‹€. ν…Œλ§ˆλŠ” light λ˜λŠ” dark λ¬Έμžμ—΄ 쀑 ν•˜λ‚˜κ³  toggleTheme λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.
  2. const ThemeToggle: React.FC<ThemeToggleProps> = ({ theme, toggleTheme }) => {} ThemeToggle μ»΄ν¬λ„ŒνŠΈλ₯Ό μ„ μ–Έν•˜κ³  μœ„μ˜ propsλ₯Ό λ°›μ•„μ™€μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€.
  3. theme의 prop 값에 따라 css 클래슀 이름을 μ„ νƒν•©λ‹ˆλ‹€.
  4. λ²„νŠΌμ„ ν΄λ¦­ν• λ•Œλ§ˆλ‹€ 닀크λͺ¨λ“œ ν˜Ήμ€ 라이트λͺ¨λ“œ 둜 ν…μŠ€νŠΈκ°€ μ „ν™˜λ©λ‹ˆλ‹€.

ν…Œλ§ˆ μ „ν™˜μ„ μ²˜λ¦¬ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλŠ” 비ꡐ적 κ°„λ‹¨ν•˜κ²Œ κ΅¬ν˜„μ΄ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. μœ„ μ»΄ν¬λ„ŒνŠΈλ₯Ό 이제 μ‹€μ œ ν…Œλ§ˆλ₯Ό μ μš©ν•  수 μžˆλŠ” μ»€μŠ€ν…€ ν›…κ³Ό ν•¨κ»˜ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

2. ThemeToggle μ»€μŠ€ν…€ ν›…

import { useState, useEffect } from 'react'

type Theme = 'light' | 'dark'

const useThemeToggle = (): { theme: Theme; toggleTheme: () => void } => {
  const [theme, setTheme] = useState<Theme>('light')

  const toggleTheme = () => {
    const newTheme: Theme = theme === 'light' ? 'dark' : 'light'
    setTheme(newTheme)
    localStorage.setItem('theme', newTheme)
  }

  useEffect(() => {
    const savedTheme = localStorage.getItem('theme') as Theme | null
    if (savedTheme) {
      setTheme(savedTheme)
    }

    document.body.className = `theme-${theme}`
  }, [theme])

  return { theme, toggleTheme }
}

export default useThemeToggle

μ²˜μŒμ—λŠ” μ‚¬μš©μž ν•˜λ“œμ›¨μ–΄ 섀정에 맞게 닀크λͺ¨λ“œ ν˜Ήμ€ 라이트λͺ¨λ“œλ₯Ό μ μš©ν•˜κ³ , ν† κΈ€ λ²„νŠΌμœΌλ‘œ μ „ν™˜ κΉŒμ§€ κ°€λŠ₯ν•œ λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•˜κ³  μ‹Άμ—ˆμ§€λ§Œ 아직은 연ꡬ가 더 ν•„μš”ν• κ²ƒκ°™μŠ΅λ‹ˆλ‹€.

μ΄λ²ˆμ—λŠ” 둜컬 μŠ€ν† λ¦¬μ§€λ₯Ό ν™œμš©ν•˜μ—¬ μ‚¬μš©μžκ°€ μ„ νƒν•œ ν…Œλ§ˆλ₯Ό κΈ°μ–΅ν•˜κ³  μ μš©ν•˜λŠ” κΈ°λŠ₯을 μœ„μ£Όλ‘œ κ΅¬ν˜„ν•΄λ΄€μŠ΅λ‹ˆλ‹€.

  1. useState, useEffect 훅을 κ°€μ Έμ˜΅λ‹ˆλ‹€. useStateλ₯Ό 톡해 초기 ν…Œλ§ˆ μƒνƒœλ₯Ό light 둜 μ„€μ •ν–ˆκ³  useEffectλ₯Ό 톡해 theme λ³€μˆ˜κ°€ λ³€κ²½ λ λ•Œλ§ˆλ‹€ document.body의 클래슀λ₯Ό λ³€κ²½ν•˜μ—¬ ν…Œλ§ˆ μŠ€νƒ€μΌμ„ λ³€κ²½ 적용 ν•©λ‹ˆλ‹€.
  2. const toggleTheme = () β‡’ {} λ₯Ό 톡해 ν…Œλ§ˆλ₯Ό ν† κΈ€ν•˜κ³ , λ³€κ²½λœ ν…Œλ§ˆλ₯Ό 둜컬 μŠ€ν† λ¦¬μ§€μ— μ €μž₯ν•©λ‹ˆλ‹€.
  3. return {theme, toggleThme} μ»€μŠ€ν…€ ν›…μ˜ λ°˜ν™˜ κ°’μœΌλ‘œ ν˜„μ œ ν…Œλ§ˆμ™€ ν…Œλ§ˆ λ³€κ²½ ν•¨μˆ˜λ₯Ό 객체 ν˜•νƒœλ‘œ λ°˜ν™˜ν•˜κ²Œ λ©λ‹ˆλ‹€.

μœ„μ˜ ThemeToggle μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ»€μŠ€ν…€ 훅을 ν˜ΈμΆœν•˜μ—¬ ν…Œλ§ˆ μ „ν™˜ κΈ°λŠ₯을 μ‚¬μš©ν•˜λŠ” κ°„λ‹¨ν•œ λ°©μ‹μœΌλ‘œ κ΅¬ν˜„μ΄ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

3. ν† κΈ€ λ²„νŠΌ μŠ€νƒ€μΌλ§

import React from 'react'

type ThemeToggleProps = {
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

const ThemeToggle: React.FC<ThemeToggleProps> = ({ theme, toggleTheme }) => {
  const themeClassName = theme === 'light' ? 'light-mode' : 'dark-mode'

  const buttonStyle: React.CSSProperties = {
    width: '50px',
    height: '50px',
    position: 'fixed',
    border: 'none',
    borderRadius: '50%',
    bottom: '20px',
    right: '0',
    transform: 'translate(-50%, -50%)',
    color: theme === 'light' ? 'white' : 'black',
    backgroundColor: theme === 'light' ? 'black' : 'white',
  }

  return (
    <button
      onClick={toggleTheme}
      style={buttonStyle}
      className={`themeToggle ${themeClassName}`}
    >
      {theme === 'light' ? 'πŸŒ™' : 'β˜€οΈ'}
    </button>
  )
}

export default ThemeToggle

const buttonStyle: React.CSSProperties = {} λ₯Ό μ΄μš©ν•˜μ—¬ ν…Œλ§ˆ μ „ν™˜ λ²„νŠΌμ„ μŠ€νƒ€μΌλ§ ν–ˆμŠ΅λ‹ˆλ‹€. 이 λ˜ν•œ CSS 파일둜 λ”°λ‘œ λΊ„ 수 μžˆμ—ˆκ² μ§€λ§Œ, λ‹€λ₯Έ κΈ°λŠ₯μ—μ„œ λ˜ν•œ μŠ€νƒ€μΌ 객체λ₯Ό ν•¨μˆ˜λ‘œ 생성 ν–ˆκΈ°μ— ν†΅μΌν–ˆμŠ΅λ‹ˆλ‹€. 좔후에 λ¦¬νŒ©ν† λ§μ‹œμ— μˆ˜μ •λ  수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λ ‡κ²Œ ν…Œλ§ˆλ₯Ό μ „ν™˜ν•˜λŠ” λ²„νŠΌκ³Ό μ»€μŠ€ν…€ 훅을 톡해 λ‹€μ΄λ‚˜λ―Ή ν…Œλ§ˆ λ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. μ‹œμž‘ν•˜κΈ°μ „ λ‹€μ–‘ν•œ λΈ”λ‘œκ·Έλ₯Ό μ°Έκ³ ν• λ•Œλ§Œ ν•˜λ”λΌλ„ λ„ˆλ¬΄ ν¬μŠ€νŠΈλ“€μ—μ„œ 얻을 수 μžˆλŠ” 정보가 λ‹¬λΌμ„œ κ±±μ •ν–ˆλ˜κ²ƒκ³Ό 달리 생각보닀 κ΅¬ν˜„μ΄ κ°„λ‹¨ν•˜κ²Œ λ˜μ„œ λœ»λ°–μ΄μ—ˆμŠ΅λ‹ˆλ‹€. λ‹€λ§Œ ν˜„μž¬ λΈ”λ‘œκ·Έμ— μ μš©λ˜μ–΄μžˆλŠ” λŒ“κΈ€ ν”ŒλŸ¬κ·ΈμΈμ—λŠ” ν…Œλ§ˆλ³€κ²½μ΄ μ μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 이 뢀뢄은 쑰금 더 μ—°κ΅¬ν•˜μ—¬ 적용이 ν•„μš”ν•˜λ‹€λŠ” 생각이 λ“€μ—ˆμŠ΅λ‹ˆλ‹€.

μ‘°κΈˆμ”© νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—λ„ μ΅μˆ™ν•΄μ§€κ³  있고, μ»΄ν¬λ„ŒνŠΈλ₯Ό μƒμ„±ν•˜λŠ” 것에도 μžμ—°μŠ€λŸ¬μ›Œμ§€κ³  μžˆμŠ΅λ‹ˆλ‹€. μ•žμœΌλ‘œλ„ λ§Žμ€ 고민을 ν•˜κ³  λ‹€μ–‘ν•œ κΈ°λŠ₯ κ΅¬ν˜„μ„ μ‹œλ„ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

Thank You for Visiting My Blog, Have a Good Day πŸ˜†
Β© 2023 Developer JaeHyeon, Powered By Gatsby.