
지난 시간에 이어 새 프로젝트를 계속 진행해 보겠습니다. 이번 시간에는 레이아웃 구성, 전역 스타일(Global Style), CSS-in-JS, 그리고 테마(Theme) 를 Context API와 함께 적용하는 방법을 정리했습니다.
레이아웃은 페이지의 한정된 공간에 구성 요소들을 효과적으로 배열하는 것을 의미합니다. 리액트에서는 화면에 적절한 컴포넌트를 배치하기 위한 기본적인 틀이라고 생각하면 됩니다.
레이아웃이 필요한 이유는 크게 세 가지입니다.
공통된 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> 으로 감싸기만 해도 헤더와 푸터가 자동으로 붙습니다.
프로젝트 전체에 일관된 스타일을 적용하고, 브라우저마다 기본적으로 설정된 스타일(User Agent Stylesheet)의 차이를 극복하기 위해 전역 스타일을 설정합니다.
예를 들어 크롬과 파이어폭스는 h1 태그의 기본 여백이나 폰트 크기가 조금씩 다릅니다. 이 차이를 방치하면 브라우저마다 UI가 다르게 보이는 문제가 생깁니다.
| 라이브러리 | 특징 |
|---|---|
| 에릭 마이어의 Reset CSS | 모든 기본 스타일을 완전히 0으로 초기화합니다. 가장 공격적인 방식입니다. |
| Normalize.css | 브라우저 간 차이만 맞추고, 태그 고유의 특징은 살려둡니다. |
| Sanitize.css | Normalize.css보다 현대적인 방식으로 발전된 형태입니다. |
세 방식 모두 브라우저 간 스타일 차이를 극복한다는 목적은 같지만, 얼마나 초기화할 것인가 에서 차이가 납니다. Reset CSS는 h1, h2 의 크기 차이조차 모두 없애버리는 반면, Normalize와 Sanitize는 태그의 의미적 스타일을 어느 정도 보존합니다.
기존의 CSS 파일 방식에는 여러 문제점이 있었습니다.
CSS-in-JS는 이 문제들을 해결하기 위해 자바스크립트 코드 내에서 CSS를 작성하는 방식입니다.
대표적인 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 확장 프로그램을 설치하면 자동 완성을 지원받아 더 편리하게 작성할 수 있습니다.
styled-components 의 ThemeProvider 를 활용하면 프로젝트 전체에 공통 테마(색상, 폰트 등)를 지정할 수 있습니다.
테마를 사용하면 좋은 점은 다음과 같습니다.
사용자가 토글 UI를 통해 색상 테마를 바꾸는 테마 스위처 기능은 Context API와 함께 구현합니다.
핵심 동작 흐름은 아래와 같습니다.
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 를 컴포넌트마다 일일이 넘겨주지 않아도, 컴포넌트 트리 전체에서 전역으로 데이터를 공유하고 관리할 수 있습니다.