본문 바로가기
Web Study/Next.js

코딩 애플 - Dark mode 만들기

by 쿠리의일상 2023. 7. 7.

리액트에서 동적인 UI 로직

  1. state 만들고 현재 UI 상태 저장
  2. state 에 따라 UI가 어떻게 보일지 코드 작성
  3. 원할 때 state 를 변경

CSS 의 속성 prefers-color-sheme

자동으로 OS 테마에 맞춰서 CSS를 적용시켜주는 속성

https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme

 

prefers-color-scheme - CSS: Cascading Style Sheets | MDN

The prefers-color-scheme CSS media feature is used to detect if a user has requested light or dark color themes. A user indicates their preference through an operating system setting (e.g. light or dark mode) or a user agent setting.

developer.mozilla.org

.theme-a {
  background: #dca;
  color: #731;
}
@media (prefers-color-scheme: dark) {
  .theme-a.adaptive {
    background: #753;
    color: #dcb;
    outline: 5px dashed #000;
  }
}

위의 속성이 있긴 하지만 다크모드 버튼과 기능을 유저가 원하는대로 사용할 수 있게 추가해준다.

 


layout.js 를 꾸며줘야 한다.

아래처럼 state 를 지정해주고, css 로 밝은화면 어두운화면 두가지 버전을 만들어 둔다.

// state 로 UI 모드의 여부 저장
let [mode, setMode] = useState('light')

<body className={darkMode}>
<button onClick={()=>{ setMode('dark') }}>다크모드</button>

이때 body 태그에 넣어줘야 모든 요소에 적용이 가능하므로 layout.js 에 state 처리가 필요한 것

-> 하지만 state 는 클라이언트 컴포넌트에서 사용할 수 있으므로 변경해줘야 한다.

 

useState 의 단점

  • state 값은 영구저장이 되지 않고 새로고침하면 리셋 됨
  • state 변경 함수는 서버 컴포넌트와 클라이언트 컴포넌트간 전달이 어렵다
  • 클라이언트 컴포넌트로 만드는 경우 서버 컴포넌트의 캐싱기능 등 사용이 불가능해진다

즉, 다크모드로 변경한 유저가 새로고침되면 라이트모드가 되면 안된다.

이를 DB에 저장하여 다크모드 여부를 영구저장하는 것이 좋지만, 간단하게 브라우저 저장공간을 활용해본다.

 

검사 > 어플리케이션 > Storage에 위치

localStorage

  • 키와 값으로 이루어진 정보가 담김
  • 최대 5mb 까지 문자나 숫자만 저장 가능, 다만 객체나 배열 등도 JSON으로 변경하면 저장이 가능함
  • 브라우저를 껐다켜도 정보가 남아있으며 직접 정리하지 않으면 계속 남아있다(반영구적)
localStorage.setItem('key', 'value'); // 데이터 저장

localStorage.getItem('key'); // 데이터 읽기

localStorage.removeItem('key'); // 데이터 삭제
로컬스토리지는 전역으로 접근이 가능하다.
단, 로컬스토리지도 자바스크립트 문법에 일부이므로 클라이언트 컴포넌트 안에서만 사용 가능하다.

 

클라이언트 컴포넌트도 서버에서 렌더링 가능한 것은 미리 렌더링되는 특성 때문에

localStorage로 접근이 불가능할 수 있으므로 조건문 처리를 추가해주는 것이 좋다.

useEffect(() => {
	if(typeof window != 'undefined') {
    	localStorage.setItem('key', 'value');
    }
}, []);

브라우저에선 window 라는 전역객체가 있으므로 위처럼 if(typeof window != 'undefined') 라고 써주면

서버인지 브라우저인지 확인이 가능하다.

다만 늦게 읽어질 수 있기 때문에 늦어져도 괜찮은 문제에 관해서만 위처럼 처리해준다.

 

session Storage

  • 브라우저가 꺼지면 정보가 사라지는 휘발성 저장소
  • 키와 값으로 이루어진 정보가 담기며 로컬스토리지와 유사
sessionStorage.setItem('key', 'value'); // 데이터 저장

sessionStorage.getItem('key'); // 데이터 읽기

sessionStorage.removeItem('key'); // 데이터 삭제

 

cookies

  • 키와 값으로 이루어진 정보
  • 사이트당 보통 50개까지, 총 4kb 까지 저장이 가능하며
  • 유효기간 설정이 가능 (최대 400일)
  • GET, POST 요청 시 자동으로 쿠키가 포함되어 보내진다.
import { cookies } from 'next/headers'
cookies().set('key', 'value') // 값 저장

cookies().get('key') // 값 읽기

요청 받은 쿠키 정보들을 확인 가능!

document 로 접근 가능!

document.cookie = '쿠키이름=값' // 데이터 저장, 유효기간의 default 값은 브라우저가 꺼지면 사라지는 것임

document.cookie = '쿠키이름=값; max-age=3600' // ;으로 옵션을 구분해주며, max-age 는 유효기간임
쿠키는 로컬스토리지와 달리 서버 컴포넌트에서 출력이 가능하다!
쿠키 생성의 다른 방법
1. 서버 API 코드
2. 미들웨어 사용

 

로컬스토리지는 위와 같은 이슈가 있으므로 다크모드 기능은 쿠키를 사용해준다.


 

'use client'
import React from 'react'
import { useEffect } from 'react'
import { useRouter } from "next/navigation"

export default function DarkMode() {
  let router = useRouter()

  useEffect(() => {
    let isCookie = ('; ' + document.cookie).split(`; mode=`).pop().split(';')[0];
    if(isCookie === '') // 쿠키값이 아예 없을 때 나오는 에러 방지
      document.cookie = `mode=${mode}; max-age=3600`;
  }, [])
  return (
    <div onClick={() => {
      let 쿠키값 = ('; '+document.cookie).split(`; mode=`).pop().split(';')[0]
      if (쿠키값 == 'light') {
        document.cookie = 'mode=dark; max-age=' + (3600 * 24 * 400)
        router.refresh()
      } else {
        document.cookie = 'mode=light; max-age=' + (3600 * 24 * 400)
        router.refresh()
      }
    }}> 🌙 </div>
  )
}

document 로 쿠키에 접근하기 위해 클라이언트 컴포넌트로 만들어준다.

isCookie 의 값은 문자들을 split 해서 mode= 다음에 넣어준 value 값을 파싱해준 결과이다.

이 값이 없을 때, 즉 쿠키가 아직 주어지지 않았을 때 공백이 들어가며, 이때 쿠키를 최초로 만들어준다. 

그리고 다크모드 버튼을 누를 때도 같은 과정을 거치게 만들어서 변경을 시켜주는데 다만 쿠키 값을 바꾼다고 페이지가 새로고침 되지 않는다. 그렇기에 useRouter 훅을 사용하여 새로고침처리를 해준다. (쿠키는 새로고침을 해줘야 변경된 값이 반영된다)

추가로 쿠키는 값을 넣어주면 그때그때마다 덮어씌워지는 원리라서 위처럼 처리한 것.

import { useRouter } from "next/navigation"
let router = useRouter();

// ...

router.refresh(); // 새로고침 됨

 

추가 도전

1. 다크모드일 때 ☀️, 라이트모드일 때 🌙 로 변경

2. 쿠키 꺼내는 코드가 많으므로 state 로 처리

export default function DarkMode({mode}) {
  let router = useRouter();
  const [modeCookie, setModeCookie] = useState('');

  useEffect(() => {
    if(modeCookie === '')
      document.cookie = `mode=${mode}; max-age=3600`;
  }, []);

  return (
    <div onClick={() => {
      if (modeCookie == 'light') {
        setModeCookie('dark');
        document.cookie = `mode=dark; max-age=3600`;
        router.refresh();
      } else {
        document.cookie = `mode=light; max-age=3600`;
        setModeCookie('light');
        router.refresh();
      }
    }}> {modeCookie === 'light' ? '🌙' : '☀️'} </div>
  )
}