개인 프로젝트 | Linkeeper - 5
React로 공통 레이아웃 구현하기
헤더와 사이드바, 푸터와 같은 레이아웃은 페이지마다 반복되는 UI 요소이다. 개발 과정에서 반복되는 코드는 유지보수를 어렵게 하고 코드의 가독성을 떨어뜨린다. 이렇게 반복되는 레이아웃 요소를 별도의 컴포넌트로 분리하여 다양한 페이지에서 재사용하면 코드의 중복을 줄이고 개발 효율성을 높일 수 있다.
시맨틱 태그로 레이아웃을 만들어야 하는 이유
우리가 흔히 보는 웹 사이트들을 살펴보면 디자인은 서로 달라 보여도 그 구조는 크게 다르지 않다.
- 사이트 제목이나 로고, 검색 창이 있는(header)
- 여러 내용이 있는 본문(contents)
- 본문 외 내용을 나타내는 사이드바(sidebar)와 푸터(footer)
이를 토대로 HTML5에서는 태그 이름만 보고도 문서 구조에서 어떤 역할을 하는지 쉽게 이해할 수 있는 ‘시맨틱 태그(semantic tag)’를 추가했다.
레이아웃을 만들 때 시맨틱 태그를 사용하지 않더라도 레이아웃을 만들 수 있고 웹 브라우저에 나타나는 모습은 동일하다. 하지만 레이아웃을 구성할 때 시맨틱 태그를 적절히 활용하는 것은 웹 접근성과 검색 엔진 최적화(SEO)에 중요한 역할을 한다.
시각장애인들은 웹 사이트를 이용할 때 화면 낭독기 같은 웹 보조 도구를 이용하는데, 시맨틱 태그를 통해 구별할 수 있으므로 그만큼 사이트 내용을 정확히 전달할 수 있다.
또한 시맨틱 태그로 작성한 소스코드는 어느 부분이 제목이고 메뉴이고 실제 내용인지 쉽게 알 수 있다. 이렇게 소스코드만으로 문서 내용을 알 수 있어 사이트를 검색할 때 필요한 내용을 정확히 찾을 수 있다.
기본 레이아웃 구현하기
1. 레이아웃 설계
레이아웃 구성은 다음과 같다. 헤더와 푸터는 상단과 하단에 고정되어 있어야 하며, 헤더와 사이드바는 스크롤 해도 위치가 고정되어 있어야 한다. Main 컨텐츠는 사이드바 우측에 위치해야 하며 페이지를 이동하면 그에 맞는 콘텐츠가 표시된다.
2. 루트(Root) 컴포넌트에서 레이아웃 컴포넌트 조합
페이지를 이동해도 Header, SideBar, Footer는 항상 같은 위치에 고정되어 있고 Main 영역의 콘텐츠만 변경되도록 구현하고자 한다.
styled-components
라이브러리를 사용해 스타일을 적용하였다. St
접두사는 styled-components
로 만들어진 컴포넌트임을 명시하여 일반 React 컴포넌트와 스타일 컴포넌트를 구분하기 위해 사용하였다.
상위 컴포넌트에서 레이아웃 컴포넌트를 조합하여 기본 레이아웃을 구성한다. StLayoutContainer
는 애플리케이션의 전체 레이아웃을 구성하는 컨테이너 역할이다.
CSS Grid는 격자 내에서 요소를 자유롭게 배치할 수 있어, 복잡한 레이아웃을 쉽게 구성할 수 있다.
적용한 CSS에 대해서는 자세히 다루지 않는다. 참고: 이번에야말로 CSS Grid를 익혀보자
// Root.tsx
import { Outlet } from 'react-router-dom';
import { Footer, Header, Main, Sidebar } from './layouts';
import { GlobalStyle } from '@styles/GlobalStyle';
import styled from 'styled-components';
export default function Root() {
return (
<StLayoutContainer>
<Header />
<Sidebar />
<Main>
<Outlet />
</Main>
<Footer />
<GlobalStyle />
</StLayoutContainer>
);
}
const StLayoutContainer = styled.div`
margin: 0 auto;
display: grid;
grid-template-columns: var(--sidebar-width) auto;
grid-template-rows: var(--header-height) max-content var(--footer-height);
max-width: var(--max-width);
`;
CSS 변수를 사용하여 레이아웃의 넓이와 높이를 변수로 선언하고 필요한 곳에서 재사용하였다. 만약 레이아웃의 높이와 같은 값이 변경될 때, 변수의 값만 수정하면 해당 변수를 사용하는 모든 곳이 자동으로 업데이트되어 유지 보수가 쉬워지기 때문이다.
// GlobalStyle.ts
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
(...)
:root {
--max-width: 1800px;
--sidebar-width: 300px;
--header-height: 56px;
--footer-height: 56px;
--layout-padding: 1rem;
}
`;
3. 각 레이아웃 컴포넌트 구현
루트 컴포넌트에서 사용될 각 레이아웃 컴포넌트를 구현한다. 시맨틱 태그를 사용하여
📦linkeeper
┣ 📂src
┃ ┣ 📂components
┃ ┣ 📂layouts
┃ ┃ ┣ 📜Footer.tsx
┃ ┃ ┣ 📜Header.tsx
┃ ┃ ┣ 📜index.ts
┃ ┃ ┣ 📜Main.tsx
┃ ┃ ┗ 📜Sidebar.tsx
3-1. layouts/Header 컴포넌트
import styled from 'styled-components';
export default function Header() {
return (
<StHeader>
<div className="logo">
<Link to="/">
<h1>Linkeeper</h1>
</Link>
</div>
</StHeader>
);
}
const StHeader = styled.header`
grid-column: 1 / -1;
grid-row: 1;
position: fixed;
width: 100%;
max-width: var(--max-width);
height: var(--header-height);
background-color: #f8f9fa;
`;
3-2. layouts/Sidebar 컴포넌트
import styled from 'styled-components';
export default function Sidebar() {
return <StSidebar>Sidebar</StSidebar>;
}
const StSidebar = styled.aside`
margin-top: var(--header-height);
grid-column: 1;
grid-row: 2 / -1;
position: fixed;
width: var(--sidebar-width);
height: calc(100vh - var(--header-height));
background-color: #e9ecef;
`;
3-3. layouts/Main 컴포넌트
PropsWithChildren
은 TypeScript에서 React 컴포넌트의 props에 children
타입을 정의할 때 사용한다. 전달 받은 children
에는 Outlet
컴포넌트가 현재 위치에 매칭되는 컴포넌트로 대체되어 전달된다.
import { PropsWithChildren } from 'react';
import styled from 'styled-components';
export default function Main({ children }: PropsWithChildren) {
return <StMain>{children}</StMain>;
}
const StMain = styled.main`
padding: 0 1rem var(--footer-height) 1rem;
grid-column: 2;
grid-row: 2 / -1;
min-height: calc(100vh - var(--header-height));
`;
3-4. layouts/Footer 컴포넌트
import styled from 'styled-components';
export default function Footer() {
return <StFooter>Footer</StFooter>;
}
const StFooter = styled.footer`
grid-column: 2 / -1;
grid-row: 3;
width: 100%;
height: var(--footer-height);
background-color: #f8f9fa;
`;
댓글남기기