일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- JavaScript
- [파이썬 실습] 중급 문제
- 간단한 날씨 웹 만들기
- 엘리스 ai 트랙
- 엘리스
- 자바스크립트 sort()
- leetcode
- 자바스크립트 날씨
- 리트코드
- 코딩부트캠프
- [AI 5기] 연습 문제집
- 프론트개발공부
- 개발일기
- 자바스크립트 reduce()
- 삼항연산자
- 프로그래머스
- 프론트개발
- 자바스크립트
- RN 프로젝트
- 개발공부
- 코드스테이츠
- [파이썬 실습] 기초 문제
- 부트캠프
- HTML
- 자바스크립트 split()
- [파이썬 실습] 심화 문제
- 엘리스 AI 트랙 5기
- reactnativecli
- 날씨 웹 만들기
- 자바스크립트 날씨 웹 만들기
- Today
- Total
개발조각
[모두의 이력서_10-11일차] 다크모드(React Context API + Next.js + TS + styled-component) 본문
[모두의 이력서_10-11일차] 다크모드(React Context API + Next.js + TS + styled-component)
개발조각 2023. 4. 5. 18:24다크모드는 나중에 구현할까 했지만 프로젝트 규모가 더 커지기 전에 빨리 구현하는 게 좋겠다는 판단이 들어 구현하게 되었습니다. :)
- 프로젝트 규모가 더 커지기 전에 빠르게 구현
- 토글을 통해 변경
- 페이지 이동, 새로고침, 페이지를 나갔다 들어봐도 적용한 테마가 유지 되도록 localStorage를 사용
1. context API를 사용한 이유
다크모드 구현 방법은 대표적으로 contex tAPI, 상태관리(redux, recoil)를 사용해서 구현할 수 있습니다.
하지만 context API를 선택한 이유는 light모드, dark모드만 만들 생각인데 상태관리를 쓸 필요가 있을까 해서 context API를 사용하게 되었습니다.
(사실은 context API를 사용해보고 싶어서 사용해보았습니다.)
2. theme 설정
기존에 styled-component를 초기세팅을 했을 때 theme를 설정했습니다.
초기세팅 시에는 라이트모드, 다크모드를 생각 안 하고 세팅한 거라 이번에는 라이트모드, 다크모드 세팅에 맞게 수정했습니다.
Next에서 styled-component 초기 세팅하는 법을 알고 싶으면 아래 링크로👇
https://development-piece.tistory.com/309
[모두의 이력서_7일차] styled-components + Next.js + TS
파일구조 global.ts // 전역스타일 지정 theme.ts // 공통적으로 사용할 스타일 지정 styled.d.ts // theme파일에 들어갈 변수들의 타입을 정의 1. styled-components 설치하기 styled-components를 타입스크립트에서
development-piece.tistory.com
// theme.ts
import { DefaultTheme } from "styled-components";
export type colorsType = typeof colors;
export type fontSizesType = typeof fontSizes;
export type commonType = typeof common;
export type deviceType = typeof device;
const colors = {
// main
main: "#FF6678",
// gray
grayText: "#767676",
lightTheme: {
text: "#333",
bg: "#fff",
lightBg: "#F8F8F8",
border: "#E1E2E3",
},
darkTheme: {
text: "#E8E8E8",
bg: "#1e1f21",
lightBg: "#353638",
border: "#292a2d",
},
};
const fontSizes = {...};
const common = {...};
const device = {...};
const theme: DefaultTheme = {
colors,
fontSizes,
device,
common,
};
export default theme;
저는 이와 같이 세팅해 주었습니다.
${({theme}) => theme.color.lightTheme.text} 하면 나옵니다.
3. context API 설정
context
App 안에서 전역적으로 사용되는 데이터를 여러 컴포넌트끼리 공유 할 수 있는 방법을 제공해 줍니다.
그래서 수많은 컴포넌트들이 필요한 전역적인 데이터를 전달하기에 굉장히 편리합니다.
그렇다고 prop 대신 context를 사용해서는 안됩니다.
context를 사용하면 컴포넌트를 재사용하기 어려워질 수 있기 때문에 꼭 필요할 때만 사용해야 됩니다.
context폴더 구조
src
|- context
|- themeContext.ts
|- type.ts
type.ts
import { Dispatch, SetStateAction } from "react";
export interface IsDark {
isDark: boolean;
setIsDark: Dispatch<SetStateAction<boolean>>;
}
타입스크립트를 사용하기 때문에 value에 전송해 줄 값을 미리 지정했습니다.
themeContext.ts
import { createContext } from "react";
import { IsDark } from "./type";
export const ThemeContext = createContext<IsDark | null>(null);
상태 관리를 하기 위해 createContext를 생성해 주었습니다.
_app.tsx
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { ThemeProvider } from "styled-components";
import Header from "../components/layout/header/Header";
import Float from "../components/layout/Float"; // 다크모드 버튼, top 버튼
import theme from "../styles/theme";
import Global from "../styles/global";
import { ThemeContext } from "../context/themeContext";
const _app = ({ Component }: AppProps) => {
const [isDark, setIsDark] = useState(false);
return (
<>
<main className={notoSansKr.className}>
<ThemeContext.Provider value={{ isDark, setIsDark }}>
<ThemeProvider theme={theme}>
<Global mode={isDark ? "darkTheme" : "lightTheme"} />
<Header />
<Float />
<Component />
</ThemeProvider>
</ThemeContext.Provider>
</main>
</>
);
};
export default _app;
(코드가 길어서 context에서 사용되는 것만 편집했습니다.)
ThemeContextProvider는 value라는 prop를 받고, value안에 전달하고자 하는 데이터를 집어넣어 주면 됩니다.
ThemeContextProvider로 감싸고 있는 모든 하위 컴포넌트는 value 집어넣은 데이터를 접근할 수 있습니다.
여기서는 [isDark, seIsDark]의 useState를 이용해서
isDark가 true이면 다크모드, false면 라이트모드로 변할 수 있도록 했습니다.
4. localstorage 설정하기
페이지 이동, 새로고침, 페이지를 나갔다 들어봐도 적용한 테마가 유지되도록
다크모드이면 localStorage에 담고, 라이트모드면 localStorage를 제거하도록 만들었습니다.
localStorage 추가, 읽기, 삭제
setItem() - key, value 추가
localStorage.setItem(key, value)
getItem() - value 읽어 오기
localStorage.getItem(key)
removeItem() - item 삭제
localStorage.removeItem(key);
_app에서는 localStorage에 넣어줄 value가 있는지 확인을 해줄거기 때문에 아래와 같이 넣어주었습니다.
(저는 여기서는 key값은 "isDark", value값은 "Y"로 넣어줄 겁니다.)
// _app.tsx
const derkMode = localStorage.getItem("isDark")
const [isDark, setIsDark] = useState(derkMode);
하지만.... Next에서 localStorage를 사용하면 다음과 같은 서버 에러가 뜹니다.
서버에러가 뜨는 이유
간단하게
Next.js는 SSR이기 때문에
자세하게
Next.js는 client-side를 렌더하기 전 server-side 렌더를 수행합니다.
Next.js에서 제공하는 Server Side Rendering(SSR)에서는 window, document 같은 브라우저 전역 객체를 사용할 수 없어 window 객체는 client-side에만 존재하게 됩니다.
=> 따라서, 페이지가 client에 로드되고 window 객체가 정의될 때까지 localStorage에 접근할 수 없습니다.
해결방법
window 객체의 유무를 살피고 window 함수에 접근한다면 에러메시지를 발생시키지 않으면 됩니다.
typeof window !== 'undefined'
const derkMode =
typeof window !== "undefined" ? localStorage.getItem("isDark") : null;
const [isDark, setIsDark] = useState(derkMode ? true : false);
저는 이렇게 써주었습니다.
_app.tsx 최종 코드
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { ThemeProvider } from "styled-components";
import Header from "../components/layout/header/Header";
import Float from "../components/layout/Float"; // 다크모드 버튼, top 버튼
import theme from "../styles/theme";
import Global from "../styles/global";
import { ThemeContext } from "../context/themeContext";
const _app = ({ Component }: AppProps) => {
const derkMode =
typeof window !== "undefined" ? localStorage.getItem("isDark") : null;
const [isDark, setIsDark] = useState(derkMode ? true : false);
return (
<>
<main className={notoSansKr.className}>
<ThemeContext.Provider value={{ isDark, setIsDark }}>
<ThemeProvider theme={theme}>
<Global mode={isDark ? "darkTheme" : "lightTheme"} />
<Header />
<Float />
<Component />
</ThemeProvider>
</ThemeContext.Provider>
</main>
</>
);
};
export default _app;
5. 라이트, 다크모드 버튼 생성
라이트모드면 removeItem을 사용해요 key값을 제거해 주었고,
다크모드면 setItem을 사용하여 key: "isDark", value:"Y"를 넣어주었습니다.
(라이트모드: isDark false, 다크모드: isDark true)
component > layout > Float.tsx
import { useContext } from "react";
import { ThemeContext } from "../../context/themeContext";
import { IsDark } from "../../context/type";
import * as styled from "../../styles/components/layout/Float";
import { Moon, Sun } from "@styled-icons/heroicons-solid";
import { ChevronUp } from "@styled-icons/boxicons-regular";
const Float = () => {
const { isDark, setIsDark } = useContext(ThemeContext) as IsDark;
const handlerIsDarkClick = () => {
if (isDark) {
localStorage.removeItem("isDark");
} else {
localStorage.setItem("isDark", "Y");
}
setIsDark(!isDark);
};
return (
<styled.FloatCon mode={isDark ? "darkTheme" : "lightTheme"}>
<button onClick={handlerIsDarkClick}>
{isDark ? <Sun /> : <Moon />}
</button>
<button>
<ChevronUp />
</button>
</styled.FloatCon>
);
};
export default Float;
styled-icons 사용해 보기(부가적 내용)
이번에는 react-icons가 아닌 styled-icons를 사용해 보았습니다.
설치하기
npm i styled-icons
사용방법
사용방법은 react-icons랑 사용방법이 똑같습니다.
import {Zap} from '@styled-icons/octicons'
const App = () => <RedZap />
색상 변경은 svg에서 변경하시면 됩니다.
아이콘은 아래 링크로 들어가셔서 사용하시면 됩니다.
Styled Icons - a Styled Components icon library
Import icons from the following icon packs as Styled Components: Bootstrap, Boxicons, Crypto Icons, Entypo, Eva Icons, Evil Icons, Feather, FluentUI System, Font Awesome, Foundation, Heroicons, Icomoon, Ionicons, Material Design, Octicons, Open Iconic, Rem
styled-icons.dev
6. 라이트, 다크모드에 따라 스타일 적용
최상단 gif를 보시면 토글버튼에서도 다크모드 라이트 모드에 따라 스타일이 변경되시는 걸 보실 수 있습니다.
저는 styled-component의 props를 사용해서 라이트, 다크 모드에 따른 스타일 변경사항을 제어했습니다.
이런 식으로 전달을 해주고
<styled.FloatCon mode={isDark ? "darkTheme" : "lightTheme"}>
스타일 파일에 props로 전달받은 mode의 타입을 정의를 하고
export const FloatCon = styled.div<{ mode: "darkTheme" | "lightTheme" }>`
mode에서 받은 값을 넣어주시면 됩니다.
background: ${({ theme, mode }) => theme.colors[mode].bg};
globals, header, footer 등등 테마가 적용될 곳에 다 적용하시면 됩니다.
최종코드
component > layout > Float.tsx
import { useContext } from "react";
import { ThemeContext } from "../../context/themeContext";
import { IsDark } from "../../context/type";
import * as styled from "../../styles/components/layout/Float";
import { Moon, Sun } from "@styled-icons/heroicons-solid";
import { ChevronUp } from "@styled-icons/boxicons-regular";
const Float = () => {
const { isDark, setIsDark } = useContext(ThemeContext) as IsDark;
const handlerIsDarkClick = () => {
if (isDark) {
localStorage.removeItem("isDark");
} else {
localStorage.setItem("isDark", "Y");
}
setIsDark(!isDark);
};
return (
<styled.FloatCon mode={isDark ? "darkTheme" : "lightTheme"}>
<button onClick={handlerIsDarkClick}>
{isDark ? <Sun /> : <Moon />}
</button>
<button>
<ChevronUp />
</button>
</styled.FloatCon>
);
};
export default Float;
Float.ts
import styled from "styled-components";
import { SpinLeft, SpinRight } from "../../common/animation";
export const FloatCon = styled.div<{ mode: "darkTheme" | "lightTheme" }>`
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
gap: 8px;
button {
width: 36px;
height: 36px;
border: 1px solid ${({ theme, mode }) => theme.colors[mode].border};
border-radius: 50%;
background: ${({ theme, mode }) => theme.colors[mode].bg};
transition: all 0.3s;
&:hover {
background: ${({ theme, mode }) => theme.colors[mode].lightBg};
}
}
button:first-of-type {
animation: ${({ mode }) => (mode === "darkTheme" ? SpinRight : SpinLeft)} 1s;
svg {
width: 20px;
transform: rotation;
}
}
`;
참고자료
context API 설명 및 사용법 + 라이트, 다크모드 실습
https://www.youtube.com/watch?v=LwvXVEHS638&list=PLZ5oZ2KmQEYjwhSxjB_74PoU6pmFzgVMO&index=6
contextAPI로 라이트, 다크 모드 만들기
리액트 다크모드 구현하기 feat. styled-components & context API
사용자 경험을 최상으로 이끌어주는 디자인 트렌드 다크모드 UI.애플, 구글, 인스타그램, 페이스북 등 세계적인 브랜드들이 이 다크모드 기능을 애용하기 시작하며 UI/UX에 필수적인 기능 중 하나
velog.io
[React.js] 다크모드 (Emotion.js + Next.js + TypeScript)
다크모드를 구현 가즈아앗~!
velog.io
localStorage 사용법
https://hianna.tistory.com/697
[Javascript] localStorage 사용법 (읽기, 쓰기, 삭제, 키목록 등)
이번에는 localStorage 사용법을 정리해보았습니다. localStorage란? localStorage에 아이템 추가, 읽기 localStorage에 객체, 배열 저장하기 localStorage에 값 삭제하기 localStorage에 값 전체 삭제하기 localStorage의
hianna.tistory.com
Next에서 localStorage 오류 해결
https://brick-house.tistory.com/18
Next js에서 sessionStorage is not defined 문제 해결 방법
개발 중에 발견한 문제에 대한 해결책을 간단하게 적어두려 한다. 우선 Next Js는 SSR이라는 점을 꼭 기억해야 한다!! 무턱대고 sessionStorage 혹은 localStorage (그 외 window 함수..)를 사용하려고 하면 다
brick-house.tistory.com
https://velog.io/@qhflrnfl4324/%EB%82%91%EA%B9%A1%ED%8C%9C08-js-cookie
🍊 낑깡팜_08 : "localStorage is not defined" in Next.js with js-cookie
🔸 How to Fix "localStorage is not defined" in Next.js
velog.io
'모두의 이력서' 카테고리의 다른 글
[모두의 이력서_13일차] Next.js TS에서 Styled-components 적용 안되는 에러 (0) | 2023.04.07 |
---|---|
[모두의 이력서_12일차] Next url표시 방법, Next 특정 페이지에서 header안보이게 하기 (0) | 2023.04.06 |
[모두의 이력서_9일차] 헤더, 푸터 ui 구현(+ 반응형) (0) | 2023.04.03 |
[모두의 이력서_9일차] Next.js 이미지 넣기, Next.js에서 현재 url을 알고 싶을 때 (0) | 2023.04.03 |
[모두의 이력서_8일차] Next.js 파비콘 적용 + 구글폰트 적용하기 (0) | 2023.04.03 |