TypeScript 제네릭은 props와 API 응답의 타입 관계를 어떻게 유지하나요?
제네릭은 입력과 출력, callback 사이에서 달라지는 타입의 관계를 유지하며 같은 구조를 재사용하는 방법입니다. T로 받은 타입을 number·string·Task 등에 이어주므로 any처럼 검사를 없애지 않습니다.
적용 조건·범위: 자가 확인: BoxProps<string>으로 바꾸면 value·callback의 연산도 문자열에 맞춰야 하는 이유를 설명해 보세요. ApiResponse 선언은 외부 JSON의 실제 구조를 검증하는 함수가 아니며 실행 시 검증은 별도입니다. 근거: TypeScript 제네릭 공식 문서 (2026-10-12).
본문 근거: TypeScript 제네릭 공식 문서
직접 확인: 오류에서 수정까지
TypeScript 제네릭 쉽게 이해하기: props와 API 응답 타입으로 감 잡기 · 독립 실습
얻을 결과: first<T>가 입력 배열 원소와 반환 타입을 연결하고 빈 배열을 undefined로 표시합니다.
예제 크게 보기 (새 탭)단독 검사는 tsconfig.json이 없는 새 폴더에서 example.ts 한 파일씩 교체하며 진행합니다. TypeScript가 없다면 설치 글에서 Node.js/npm·TypeScript 준비를 확인하세요. 타입 검사 성공은 코드 실행 성공과 구분합니다.
독자 질문: 첫 항목을 꺼낼 때 값의 타입을 잃지 않으려면?
얻을 결과: first<T>가 입력 배열 원소와 반환 타입을 연결하고 빈 배열을 undefined로 표시합니다.
오류 코드를 별도 example.ts에 복사합니다.
function first<T>(items: T[]): T | undefined { return items[0]; }
const title: string = first([1, 2]);
예상 타입 오류 (TS2322): number | undefined는 string에 할당할 수 없습니다. 실제 결과 타입을 사용하고 빈 배열 가능성을 처리합니다.
위 코드를 다음 수정 예제로 교체합니다.
function first<T>(items: T[]): T | undefined { return items[0]; }
const count: number | undefined = first([1, 2]);
console.log(count);
npx tsc --strict --noEmit example.ts
기존 최소 예제 확인 기록: TypeScript 7.0.2의 별도 최소 예제에서 오류 TS2322, 수정 후 exit 0 확인. TaskFlow 전체 체크포인트·브라우저 실행은 미확인.
다음 학습: 11회차에서 extends와 keyof로 제네릭 helper를 제한합니다.
identity 함수
아래 개념 조각에서 참조하는 학습용 타입과 값입니다. 프로젝트 파일을 import하지 않습니다. 같은 이름을 다시 선언하는 비교 코드는 별도 파일에서 확인하세요.
type Task = { id: string; title: string; completed: boolean };
const tasks: Task[] = [{ id: '1', title: '학습', completed: false }];
function identity<T>(
value: T,
): T {
return value;
}
호출:
const name =
identity('TypeScript');
const count =
identity(10);
각 호출에서 T는 string, number로 추론됩니다.
입력 타입과 반환 타입의 관계가 유지됩니다.
any와 차이
function identityAny(
value: any,
): any {
return value;
}
반환 타입 정보가 사라집니다.
const value =
identityAny('hello');
value.notExists();
any는 이런 호출도 막지 못합니다.
generic:
const value =
identity('hello');
value는 string으로 유지됩니다.
배열 helper
function first<T>(
items: T[],
): T | undefined {
return items[0];
}
const firstTask =
first(tasks);
tasks가 Task[]이면 결과는 Task | undefined입니다.
API 응답
type ApiResponse<T> = {
data: T;
status: number;
};
type TaskResponse =
ApiResponse<Task>;
type TaskListResponse =
ApiResponse<Task[]>;
같은 응답 구조를 여러 데이터 타입에 재사용할 수 있습니다.
독립 확인: props와 응답에서 같은 타입 관계 쓰기
React 화면 없이 props의 데이터·callback 계약만 확인하는 타입 예제입니다. tsconfig.json이 없는 별도 연습 폴더의 props-api.ts에 아래 전체 내용을 저장합니다. 같은 이름을 쓰는 위 개념 조각과 합치지 않습니다.
type BoxProps<T> = {
value: T;
onChange: (value: T) => void;
};
type ApiResponse<T> = {
data: T;
status: number;
};
const props: BoxProps<number> = {
value: 1,
onChange(next) {
console.log(next.toFixed(0));
},
};
const response: ApiResponse<string> = {
data: "준비 완료",
status: 200,
};
props.onChange(2);
console.log(response.data.toUpperCase());
npx tsc --strict --noEmit props-api.ts로 검사합니다. 기대 결과는 value와 onChange가 number로 연결되고 응답 data는 string으로 유지되어 정상 코드는 허용되는 것입니다. props.onChange(2)의 인수를 문자열 “2”로 바꾸거나 response.data에 숫자를 넣으면 타입 오류가 나야 합니다. 타입 검사는 위 Console 호출을 실행하지 않습니다. 추가 예제는 Node.js 24.18.0의 타입 제거 실행에서 2와 준비 완료 출력을 확인했습니다. 이 추가 예제의 tsc 타입 검사와 오류 인수 사례는 미확인입니다.
자가 확인: BoxProps<string>으로 바꾸면 value·callback의 연산도 문자열에 맞춰야 하는 이유를 설명해 보세요. ApiResponse 선언은 외부 JSON의 실제 구조를 검증하는 함수가 아니며 실행 시 검증은 별도입니다. 근거: TypeScript 제네릭 공식 문서 (2026-10-12).
함수 타입
type Mapper<
TInput,
TOutput
> = (
value: TInput,
) => TOutput;
두 타입 사이의 관계를 표현할 수 있습니다.
명시적 type argument
대부분은 추론을 우선합니다.
first(tasks);
필요할 때만 직접 작성합니다.
first<Task>([]);
빈 배열처럼 추론 정보가 부족한 경우 도움이 될 수 있습니다.
generic이 필요 없는 경우
function getTaskTitle(
task: Task,
): string {
return task.title;
}
Task만 처리하는 도메인 함수라면 generic으로 바꿀 이유가 없습니다.
function getTitle<T>(
value: T,
) {
...
}
오히려 어떤 property를 쓸 수 있는지 불분명해집니다.
학습 완료 체크리스트
BoxProps의 value와 onChange 매개변수가 같은 T를 쓰는 이유, ApiResponse의 status는 같아도 data만 달라지는 이유를 설명해 보세요. 빈 배열의 undefined 처리도 재사용성과 별개의 결과 계약입니다.
작은 변형으로 확인하기
first([])의 결과는 undefined입니다. first(['A'])는 string | undefined 관계를 유지합니다. 반환값을 쓰기 전에 undefined를 처리해 보세요.
선택 실습: 할 일 앱에 적용하기
TaskFlow는 작업을 추가·완료·삭제하는 할 일 앱입니다. 이 선택 실습에서는 JavaScript 코드를 TypeScript로 확장합니다.
Task 전용으로 중복된 helper를 generic으로 만들 수 있는 부분만 정리합니다.
선택: 연재 프로젝트와 전체 자료
연재 프로젝트를 이어 만드는 분을 위한 자료입니다. 이 글의 개념과 예제만 찾으셨다면 필요한 파일과 실행 방법만 펼쳐보세요.
TaskFlow 프로젝트 적용
TypeScript 제네릭 쉽게 이해하기: props와 API 응답 타입으로 감 잡기 · 단계별 TaskFlow
타입을 적용한 TaskFlow에서 작업을 추가·완료 전환·삭제하세요. 타입만 바뀐 단계는 이전과 화면이 같습니다.
예제 크게 보기 (새 탭)변경 설명과 부분 예제를 읽은 뒤, 시작·완료 파일과 적용 순서에서 필요한 선언·import·화면 연결을 포함한 전체 파일을 확인하세요. 이번 단계의 변경 이유와 적용 위치도 함께 확인합니다.
시작본과 이번 변경
TypeScript satisfies 사용법: 타입 지정·as const와 비교하기의 완료 체크포인트를 기준으로 이어갑니다. 변경하지 않는 파일은 그 시작 상태를 유지합니다.
src/core/collection.ts에 first와 last를 추가하고 빈 배열 반환을 undefined로 모델링합니다. ApiResponse와 TaskListResponse는 src/types/api.ts에서 Task를 import해 선언합니다. Task 전용 CRUD는 그대로 유지합니다.
이전 체크포인트
typescript-step-09-satisfies
이번 목표
Task 전용으로 중복된 helper를 generic으로 만들 수 있는 부분만 정리합니다.
전체 파일·코드 보기
src/core/collection.ts
export function first<T>(
items: T[],
): T | undefined {
return items[0];
}
export function last<T>(
items: T[],
): T | undefined {
return items[
items.length - 1
];
}
API type
export type ApiResponse<T> = {
data: T;
status: number;
};
export type TaskListResponse =
ApiResponse<Task[]>;
generic으로 바꾸지 않는 함수
export function toggleTask(
tasks: Task[],
id: string,
): Task[] {
...
}
Task 도메인 로직은 Task 전용으로 유지합니다.
검증
npx tsc --noEmit
중간부터 시작하는 전체 파일과 적용 순서
시작 ZIP은 직전 프로젝트 단계 typescript-step-09-satisfies의 실제 완료 파일입니다. 프로젝트 순서를 이어가는 기준이며 사이트의 기술 읽기 순서와는 구분합니다.
- 시작 ZIP을 새 폴더에 풉니다. 이 폴더가 작업 루트이며 기존 체크포인트 파일을 별도로 찾을 필요가 없습니다.
- first·last와 ApiResponse를 추가합니다. 본문의 생략된 toggleTask 부분 코드는 실행 파일로 복사하지 않고 기존 전체 구현을 유지합니다.
- 아래 변경 파일을 같은 상대 경로에 추가하거나 전체 교체합니다. 표시하지 않은 파일과 images는 시작본 그대로 유지합니다. package-lock.json은 완료 ZIP에 있는 파일을 함께 적용합니다.
- 작업 루트에서 명령을 실행하고 예상 결과를 확인합니다. 완료 ZIP은 직접 적용한 결과를 비교하는 기준입니다.
추가·교체 파일
CHANGES.md— 전체 교체README.md— 전체 교체src/core/collection.ts— 추가src/types/api.ts— 추가
실행과 확인
npm ci
npm run dev
npm run typecheck
npm test
npm run build
개발 서버는 실행 상태로 두고 다른 터미널에서 검사 명령을 실행하세요. Node.js 24.18.0에서 검증했습니다. JavaScript는 http://127.0.0.1:4173, TypeScript는 Vite 실행 출력의 주소로 접속합니다.
예상 결과: 빈 배열의 first는 undefined, [1,2]의 last는 2이며 ApiResponse가 Task[]를 보존합니다.
기존 ZIP 검증 기록: TypeScript strict 검사·긍정/부정 타입 계약·런타임 검사·Vite 빌드 통과. 시작 ZIP에 이 글의 변경 파일만 적용한 결과와 완료 ZIP의 모든 파일 내용이 일치했습니다. 브라우저 화면 배치·실제 기기·외부 CDN 가용성은 이 검사에 포함하지 않았습니다.
이 절의 전체 파일은 ZIP과 일치합니다. 위쪽 원문의 짧은 예제나 생략 표시가 있는 부분 코드는 개념·변경 위치 설명용입니다. 서버 저장과 인증은 제공하지 않으며 JSON fixture는 로컬 읽기 실습용입니다.
복사할 변경 파일 전체 코드
src/core/collection.ts — 전체 파일
export function first<T>(
items: T[],
): T | undefined {
return items[0];
}
export function last<T>(
items: T[],
): T | undefined {
return items[
items.length - 1
];
}
src/types/api.ts — 전체 파일
import type { Task } from './task.js';
export type ApiResponse<T> = {
data: T;
status: number;
};
export type TaskListResponse =
ApiResponse<Task[]>;
완료 체크포인트
typescript-step-10-generics
다음 학습
다음 글에서는 generic이 최소한 어떤 구조를 가져야 하는지 extends constraint와 keyof를 이용해 제한합니다.
다음 글: TypeScript 제네릭 제약과 keyof 안전한 helper 만들기
글 소개·학습 준비·전체 목차

TypeScript · 10 / 22 · 필수 학습 · Generics
제네릭은 props와 API 응답에서 무엇을 재사용할까요? T로 변하는 값의 타입을 받아 입력·callback·응답 data 사이의 관계를 유지합니다. any로 검사를 없애지 않고 같은 구조를 number·string·Task 등에 적용합니다. 배열 helper와 독립 props 타입 예제로 그 관계를 확인합니다.
any처럼 타입 검사를 없애는 방식과는 다릅니다.
이번 글에서 배우는 내용
- generic type parameter
- generic 함수
- 타입 추론
- ApiResponse<T>
- 배열 helper
- any와 generic 차이
- 제네릭이 필요 없는 경우
단독 학습 선수 지식
함수·배열·union과 undefined 처리을 알면 됩니다. 이전 TaskFlow 파일 없이 직접 확인 예제로 학습할 수 있습니다.
목차
이 글이 도움이 되었나요?
읽음 기록
읽음 — / 전체 22개 · 필수 21개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.