포스트

2024.07.11 프로그래머스 - React 동적 UI 개발 2

React 기반 동적 UI 개발 - 2

레이아웃은 왜 필요할까

  • 프로젝트의 기본적인 화면 구조를 잡기 위해
  • 반복적으로 들어가야 하는 Header, Footer 를 매 화면마다 제공
  • 상황과 필요에 따라 레이아웃이 변경될 수 있도록 대비 가능

스타일 적용하기

글로벌 스타일을 왜 필요할까

  • 프로젝트에 일관된 스타일링을 적용
  • “user agent stylesheet” 로 표시되는 브라우저의 기본 스타일이 차이를 만든다.
  • 브라우저 간의 스타일 차이를 극복하기 위해 사용

  • 글로벌 css
    • 에릭마이어의 reset.css
    • normalize.css
    • sanitize.css

프로젝트에 sanitize.css 적용

  1. npm 설치

    1
    
     npm install sanitize.css
    
  2. index.tsx에 import

    1
    
     import "sanitize.css";
    

프로젝트에 styled Component 적용

  • CSS-in-JS 라이브러리
  • 필요한 이유
    1. 전역 충돌 방지
    2. css 의존성 관리의 어려움
    3. 불필요한 코드 오버라이딩
    4. 압축
    5. 상태 공유의 어려움 해결
    6. 순서와 명시
    7. 캡슐화
    8. 관심사의 분리

styled Component 사용

  • VScode 에서는 ‘vscode-styled-component’ 확장 프로그램을 설치하면 라이브러리 코드 가독성이 좋아진다.

테마 제작

테마 사용의 이유

  • UI, UX 일관성 유지
  • 유지보수 용이
  • 확장성 용이
  • 재사용성 용이
  • 사용자 정의 가능

context API 사용

  • 상태 주입 기능이 있는 context API를 통해 Provider로 제공되는 value (context)를 하위(자식) 컴포넌트에서 공통으로 사용할 수 있게 정의한다.

  • theme.tsx

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    
      import { createContext, useEffect, useState } from "react";
      import { getTheme, ThemeName } from "../style/theme";
      import { ThemeProvider } from "styled-components";
      import { GlobalStyle } from "../style/global";
        
      const DEFAULT_THEME_NAME = "light";
      const THEME_LOCALSTORAGE_KEY = "book_store_theme";
        
      interface State {
        themeName: ThemeName;
        toggleTheme: () => void;
      }
        
      export const state: State = {
        themeName: "light",
        toggleTheme: () => {},
      };
        
      export const ThemeContext = createContext<State>(state);
        
      export const BookStoreThemeProvider = ({
        children,
      }: {
        children: React.ReactNode;
      }) => {
        const [themeName, setThemeName] = useState<ThemeName>("light");
        
        const toggleTheme = () => {
          setThemeName(themeName === "light" ? "dark" : "light");
          localStorage.setItem(
            THEME_LOCALSTORAGE_KEY,
            themeName === "light" ? "dark" : "light"
          );
        };
        
        const ctx: State = {
          themeName,
          toggleTheme,
        };
        
        useEffect(() => {
          const savedThemeName = localStorage.getItem(THEME_LOCALSTORAGE_KEY);
          setThemeName(
            savedThemeName ? (savedThemeName as ThemeName) : DEFAULT_THEME_NAME
          );
        }, []);
        
        return (
          <ThemeContext.Provider value={ctx}>
            <ThemeProvider theme={getTheme(ctx.themeName)}>
              <GlobalStyle themeName={ctx.themeName} />
              {children}
            </ThemeProvider>
          </ThemeContext.Provider>
        );
      };
        
    

localStorage 사용

localStorage는 sessionStorage와 비슷하지만, localStorage의 데이터는 만료되지 않고 sessionStorage의 데이터는 페이지 세션이 끝날 떄 사라진다.

프로젝트에서 theme 에 관한 정보 (’light’ or ‘dark’) 정보를 localStorage 에 저장하여

페이지를 refresh 했을 때도, 기존에 사용하던 theme을 그대로 사용할 수 있도록 구현하였다.

  • context / theme.tsx
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
31
32
33
34
35
36
export const BookStoreThemeProvider = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  const [themeName, setThemeName] = useState<ThemeName>("light");

  const toggleTheme = () => {
    setThemeName(themeName === "light" ? "dark" : "light");
    localStorage.setItem(
      THEME_LOCALSTORAGE_KEY,
      themeName === "light" ? "dark" : "light"
    );
  };

  const ctx: State = {
    themeName,
    toggleTheme,
  };

  useEffect(() => {
    const savedThemeName = localStorage.getItem(THEME_LOCALSTORAGE_KEY);
    setThemeName(
      savedThemeName ? (savedThemeName as ThemeName) : DEFAULT_THEME_NAME
    );
  }, []);

  return (
    <ThemeContext.Provider value={ctx}>
      <ThemeProvider theme={getTheme(ctx.themeName)}>
        <GlobalStyle themeName={ctx.themeName} />
        {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  );
};

useEffect 훅을 사용하여, 의존성으로 는 빈 배열을 입력함으로써

컴포넌트의 첫 렌더링 시에만 해당 로직이 수행되도록 진행하였다.

  • 논리
    1. localStorage에서 theme의 값으로 사용되기로 한 value를 들고있는 key에서 getItem으로 값을 추출
    2. setThemeName(state Setter) 로 1번에서 추출한 savedThemeName을 할당

소감평

context API를 생각보다 더 가볍게 쓸 수 있는 방법은 처음 알았다.

그리고 강의에서도 Redux나 zustand 같이 전역 상태 관리 라이브러리가 아닌

context API 를 사용하는 것이 조금 놀란 점이었다.

혹시나 해당 프로젝트 강의가 종료될 때, 그대로 context API를 사용한다면,

스스로 zustand 나 redux 둘 중 하나로 리팩토링 하는 방향도 생각해봐야겠다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.