[Next.js + TS] 3. TypeScript로 컴포넌트 타입 잡기
2편에서 프로젝트를 띄웠다면, 이번 편은 타입을 붙인 컴포넌트를 만듭니다.
목표:Memo타입 정의 →MemoCardprops 타입 → 홈에 샘플 메모 보여 주기.
이 편에서 할 일
- 메모 데이터 모델(
Memo) 타입 만들기 - props에 타입이 있는
MemoCard컴포넌트 만들기 - 홈 페이지에 샘플 데이터로 카드 렌더하기
고급 타입(제네릭 마법, utility type 잔치)은 하지 않습니다.
props / 데이터 모델만 확실하게 잡습니다.
왜 컴포넌트에 타입을 붙이나
React만 쓸 때도 props는 넘어갑니다.
문제는 “이 컴포넌트가 뭘 받는지”가 코드만 봐서는 흐릿하다는 점입니다.
// 타입 없이
function MemoCard({ title, content }) {
return <article>...</article>;
}
title이 string인지?createdAt도 넘겨야 하는지?- 오타로
titel을 넘기면?
TypeScript를 붙이면 받는 쪽과 주는 쪽이 같은 계약을 공유합니다.
1) 데이터 모델부터: Memo 타입
프로젝트에 타입 전용 파일을 하나 둡니다.
경로: types/memo.ts
export type Memo = {
id: string;
title: string;
content: string;
createdAt: string;
};
포인트:
type으로 객체의 모양을 고정한다- 나중에 API/DB에서 같은 형태를 쓰면 한곳에서만 바꾸면 된다
id를number로 할지string으로 할지는 팀마다 다르지만, 이 시리즈는 string으로 통일한다
2) props 타입: MemoCard
컴포넌트 폴더를 만듭니다.
경로: components/MemoCard.tsx
import type { Memo } from "@/types/memo";
type MemoCardProps = {
memo: Memo;
};
export default function MemoCard({ memo }: MemoCardProps) {
return (
<article>
<h2>{memo.title}</h2>
<p>{memo.content}</p>
<time dateTime={memo.createdAt}>{memo.createdAt}</time>
</article>
);
}
읽어볼 것:
| 코드 | 의미 |
|---|---|
import type |
타입만 가져옴 (런타임 코드 아님) |
MemoCardProps |
이 컴포넌트가 받는 props 계약 |
{ memo }: MemoCardProps |
구조 분해 + 타입 적용 |
memo.title |
Memo에 있는 필드만 자동완성됨 |
실수로 memo.titlle을 쓰면 에디터/타입이 바로 에러를 냅니다.
1편에서 말했던 “안전벨트”가 여기서 체감됩니다.
3) 홈에서 샘플 데이터로 연결
app/page.tsx를 아래처럼 바꿉니다.
import MemoCard from "@/components/MemoCard";
import type { Memo } from "@/types/memo";
const sampleMemos: Memo[] = [
{
id: "1",
title: "Next.js 시작",
content: "App Router로 프로젝트 만들었다.",
createdAt: "2026-07-29",
},
{
id: "2",
title: "TypeScript props",
content: "MemoCard에 타입을 붙였다.",
createdAt: "2026-07-29",
},
];
export default function HomePage() {
return (
<main>
<h1>Memo App</h1>
<p>샘플 메모 목록</p>
<section>
{sampleMemos.map((memo) => (
<MemoCard key={memo.id} memo={memo} />
))}
</section>
</main>
);
}
확인 포인트:
sampleMemos: Memo[]→ 배열 원소가 전부Memo여야 함- 필드 하나 빠지거나 이름 틀리면 페이지에서 바로 빨간줄
key={memo.id}는 React 리스트 렌더링 기본기 (타입과는 별개, 필수)
npm run dev로 http://localhost:3000을 보면 카드 두 개가 보여야 합니다.
props를 펼치는 다른 방식 (참고)
지금은 memo 객체 하나를 통째로 넘겼습니다.
가끔은 필드를 하나씩 받기도 합니다.
type MemoCardProps = {
title: string;
content: string;
createdAt: string;
};
둘 다 틀린 건 아닙니다.
이 시리즈 추천:
데이터가 하나의 모델이면 객체로 넘긴다 (
memo: Memo)
필드가 늘어날 때마다 props를 하나씩 추가하는 것보다,
모델만 고치면 되어서 유지보수가 쉽습니다.
@/ 는 뭐지?
create-next-app에서 import alias를 켰다면:
import type { Memo } from "@/types/memo";
@/는 보통 프로젝트 루트를 가리킵니다.tsconfig.json의 paths에 정의되어 있습니다.
상대 경로 지옥(../../../types/memo)을 피하려고 쓰는 설정입니다.
자주 하는 실수
1. Memo를 값처럼 import
→ 타입만 쓸 때는 import type { Memo } 권장
2. props 타입을 안 쓰고 매개변수만 적음
→ ({ memo })만 쓰면 memo가 any에 가까워져 이득이 사라짐
3. key를 인덱스(index)로만 씀
→ 샘플에선 당장 안 터져도, 목록이 바뀌면 버그 나기 쉬움. id 사용
4. types 폴더 위치 착각
→ app/types가 아니라 프로젝트 루트의 types/ (이 시리즈 기준)
이 편에서 가져갈 것
- 데이터 모양은
type으로 먼저 고정한다 - 컴포넌트 props에도 같은 타입을 연결한다
Memo[]로 샘플을 만들면 잘못된 데이터가 즉시 드러난다- 모델 하나면 props도 객체 하나로 넘기는 편이 단순하다
다음 편 예고
[Next.js + TS] 4. 라우팅과 레이아웃
/memos목록 페이지 만들기/memos/[id]상세 페이지layout.tsx에 간단한 내비 넣기
지금은 홈에 샘플이 붙어 있는 상태입니다.
다음 편에서 URL별로 화면을 나누기 시작합니다.
시리즈 목차
- 왜 Next.js + TypeScript인가
- 프로젝트 만들고 폴더 구조 이해하기
- TypeScript로 컴포넌트 타입 잡기 ← 지금
- 라우팅과 레이아웃
- Server Component vs Client Component
- 데이터 가져오기
- 폼과 서버 액션 입문
- 정리 + 다음 학습 로드맵
'frontend > NextJs' 카테고리의 다른 글
| # [Next.js + TS] 6. 데이터 가져오기 (0) | 2026.07.29 |
|---|---|
| # [Next.js + TS] 5. Server Component vs Client Component (0) | 2026.07.29 |
| # [Next.js + TS] 4. 라우팅과 레이아웃 (0) | 2026.07.29 |
| # [Next.js + TS] 2. 프로젝트 만들고 폴더 구조 이해하기 (0) | 2026.07.29 |
| [Next.js + TS] 1. 왜 Next.js + TypeScript인가 (0) | 2026.07.29 |
