[week12] 프로젝트 : React(TypeScript) 기반의 동적 UI 개발 (2) - 03/30

Kyulee·2026년 3월 30일

TIL 

목록 보기
58/90
post-thumbnail

지난 시간에 이어 새 프로젝트를 계속 진행해 보겠습니다. 이번 시간에는 레이아웃 구성, 전역 스타일(Global Style), CSS-in-JS, 그리고 테마(Theme) 를 Context API와 함께 적용하는 방법을 정리했습니다.


1. 레이아웃 구성

레이아웃, 왜 필요할까?

레이아웃은 페이지의 한정된 공간에 구성 요소들을 효과적으로 배열하는 것을 의미합니다. 리액트에서는 화면에 적절한 컴포넌트를 배치하기 위한 기본적인 틀이라고 생각하면 됩니다.

레이아웃이 필요한 이유는 크게 세 가지입니다.

  1. 프로젝트의 기본적인 화면 구조를 잡습니다. 모든 페이지가 공통된 뼈대 위에서 동작하도록 기준을 만들어 줍니다.
  2. 반복적으로 들어가야 하는 헤더, 푸터 등을 매 화면마다 제공합니다. 중복 코드를 줄이고 일관성을 유지할 수 있습니다.
  3. 상황과 필요에 따라 레이아웃이 변경될 수 있도록 대비합니다. 예를 들어 로그인 페이지는 헤더가 없는 레이아웃을 쓰는 경우처럼요.

Children 컴포넌트의 타입

공통된 UI를 하나의 레이아웃 컴포넌트로 만들고, 내부 콘텐츠를 children 으로 전달하면 중복 코드를 효과적으로 줄일 수 있습니다. TypeScript에서 children 의 타입은 React.ReactNode 를 사용합니다.

import React from "react";

interface LayoutProps {
  children: React.ReactNode;
}

const Layout: React.FC<LayoutProps> = ({ children }) => {
  return (
    <div>
      <header>헤더 영역</header>
      <main>{children}</main>
      <footer>푸터 영역</footer>
    </div>
  );
};

export default Layout;

이렇게 하면 각 페이지에서 <Layout> 으로 감싸기만 해도 헤더와 푸터가 자동으로 붙습니다.


2. 전역 스타일 (Global Style)

왜 필요할까?

프로젝트 전체에 일관된 스타일을 적용하고, 브라우저마다 기본적으로 설정된 스타일(User Agent Stylesheet)의 차이를 극복하기 위해 전역 스타일을 설정합니다.

예를 들어 크롬과 파이어폭스는 h1 태그의 기본 여백이나 폰트 크기가 조금씩 다릅니다. 이 차이를 방치하면 브라우저마다 UI가 다르게 보이는 문제가 생깁니다.

대표적인 전역 스타일 라이브러리

라이브러리특징
에릭 마이어의 Reset CSS모든 기본 스타일을 완전히 0으로 초기화합니다. 가장 공격적인 방식입니다.
Normalize.css브라우저 간 차이만 맞추고, 태그 고유의 특징은 살려둡니다.
Sanitize.cssNormalize.css보다 현대적인 방식으로 발전된 형태입니다.

세 방식 모두 브라우저 간 스타일 차이를 극복한다는 목적은 같지만, 얼마나 초기화할 것인가 에서 차이가 납니다. Reset CSS는 h1, h2 의 크기 차이조차 모두 없애버리는 반면, Normalize와 Sanitize는 태그의 의미적 스타일을 어느 정도 보존합니다.


3. CSS-in-JS와 Styled Components

CSS-in-JS는 왜 필요할까?

기존의 CSS 파일 방식에는 여러 문제점이 있었습니다.

  1. 전역 충돌 — CSS는 기본적으로 전역 스코프라 클래스명이 겹치면 의도치 않은 스타일이 적용됩니다.
  2. 의존성 관리 어려움 — 어떤 CSS가 어떤 컴포넌트에 영향을 주는지 추적하기 어렵습니다.
  3. 불필요한 코드, 오버라이딩 — 사용하지 않는 CSS가 쌓이고, 스타일 우선순위를 맞추기 위한 오버라이딩이 늘어납니다.
  4. 압축 — 빌드 시 불필요한 CSS가 번들에 포함될 수 있습니다.
  5. 상태 공유 어려움 — JS의 상태 값을 CSS에 바로 반영하기가 번거롭습니다.
  6. 순서와 명시도 — CSS 로딩 순서나 명시도(specificity)에 따라 예상치 못한 결과가 나옵니다.
  7. 캡슐화 — 컴포넌트 단위로 스타일을 격리하기 어렵습니다.

CSS-in-JS는 이 문제들을 해결하기 위해 자바스크립트 코드 내에서 CSS를 작성하는 방식입니다.

Styled Components 기본 사용법

대표적인 CSS-in-JS 라이브러리인 styled-components 를 사용하면 컴포넌트에 스타일을 직관적으로 적용할 수 있습니다.

import styled from "styled-components";

const Button = styled.button`
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border-radius: 5px;
`;

function App() {
  return <Button>클릭하세요</Button>;
}

VSCode를 사용한다면 vscode-styled-components 확장 프로그램을 설치하면 자동 완성을 지원받아 더 편리하게 작성할 수 있습니다.


4. 테마(Theme)와 Context API

테마가 필요한 이유

styled-componentsThemeProvider 를 활용하면 프로젝트 전체에 공통 테마(색상, 폰트 등)를 지정할 수 있습니다.

테마를 사용하면 좋은 점은 다음과 같습니다.

  • UI/UX의 일관성 유지 — 색상, 폰트, 간격 등을 한 곳에서 관리합니다.
  • 유지보수가 용이 — 색상 하나를 바꿀 때 파일 전체를 뒤질 필요 없이 테마만 수정하면 됩니다.
  • 확장성 — 새로운 색상 팔레트나 토큰을 추가하기 쉽습니다.
  • 재사용성 — 동일한 테마 값을 여러 컴포넌트에서 일관되게 사용할 수 있습니다.
  • 사용자 정의 — 다크 모드처럼 사용자가 원하는 테마를 선택할 수 있게 만들 수 있습니다.

Theme Switcher with Context API

사용자가 토글 UI를 통해 색상 테마를 바꾸는 테마 스위처 기능은 Context API와 함께 구현합니다.

핵심 동작 흐름은 아래와 같습니다.

  1. 사용자는 토글 UI를 통해 웹사이트의 색상 테마(예: 라이트 ↔ 다크)를 바꿀 수 있습니다.
  2. 색상 테마는 전역 상태로 존재합니다 → Context API로 관리합니다.
  3. 사용자가 선택한 테마는 로컬스토리지에 저장해서, 페이지를 새로고침해도 유지됩니다.
import { createContext, useContext, useState } from "react";
import { ThemeProvider } from "styled-components";

const lightTheme = { background: "#ffffff", color: "#000000" };
const darkTheme  = { background: "#1a1a1a", color: "#ffffff" };

const ThemeContext = createContext({ toggleTheme: () => {} });

export const ThemeContextProvider = ({ children }: { children: React.ReactNode }) => {
  const saved = localStorage.getItem("theme");
  const [isDark, setIsDark] = useState(saved === "dark");

  const toggleTheme = () => {
    setIsDark(prev => {
      localStorage.setItem("theme", !prev ? "dark" : "light");
      return !prev;
    });
  };

  return (
    <ThemeContext.Provider value={{ toggleTheme }}>
      <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
        {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  );
};

export const useThemeContext = () => useContext(ThemeContext);

Context API를 사용하면 props 를 컴포넌트마다 일일이 넘겨주지 않아도, 컴포넌트 트리 전체에서 전역으로 데이터를 공유하고 관리할 수 있습니다.

profile
안녕하세요 매일의 배움을 기록으로 자산화하는 개발자 이규현입니다 😊

0개의 댓글