[React] TypeScript 기초
개요
React 포스트의 모든 예제 코드는 TypeScript로 작성되어 있습니다. TypeScript는 JavaScript에 타입을 추가한 언어로, 코드 작성 시 오류를 미리 잡아줍니다. React 개발에 필요한 TypeScript 기초만 간단히 정리합니다.
기본 타입
// 원시 타입
const name: string = "Alice";
const age: number = 30;
const isActive: boolean = true;
// 배열
const names: string[] = ["Alice", "Bob"];
const scores: number[] = [90, 85, 100];
// null / undefined
let value: null = null;
let data: undefined = undefined;
// any: 타입 검사 포기 (사용 자제)
let anything: any = "hello";
anything = 42; // 오류 없음
// unknown: any보다 안전한 타입 (타입 확인 후 사용)
let input: unknown = "hello";
if (typeof input === "string") {
console.log(input.toUpperCase());
}
함수 타입
// 매개변수와 반환 타입 지정
function add(a: number, b: number): number {
return a + b;
}
// 화살표 함수
const greet = (name: string): string => `안녕하세요, ${name}!`;
// 반환값 없는 함수
const log = (message: string): void => {
console.log(message);
};
// 선택적 매개변수 (?)
function createUser(name: string, age?: number): string {
return age ? `${name} (${age})` : name;
}
// 기본값 매개변수
function greetWithDefault(name: string = "익명"): string {
return `안녕하세요, ${name}!`;
}
인터페이스 (Interface)
객체의 구조(형태)를 정의합니다. React에서 Props 타입을 정의할 때 가장 많이 사용합니다.
interface User {
id: number;
name: string;
email: string;
age?: number; // 선택적 속성
readonly createdAt: Date; // 읽기 전용
}
// 사용
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
createdAt: new Date(),
};
// 인터페이스 확장
interface Admin extends User {
role: "admin" | "super-admin";
permissions: string[];
}
// React Props에서 사용
interface ButtonProps {
label: string;
variant?: "primary" | "secondary";
onClick: () => void;
disabled?: boolean;
}
function Button({ label, variant = "primary", onClick, disabled }: ButtonProps) {
return <button onClick={onClick} disabled={disabled}>{label}</button>;
}
타입 별칭 (Type Alias)
// type으로 타입 이름 정의
type Point = {
x: number;
y: number;
};
// Union 타입: 여러 타입 중 하나
type Status = "loading" | "success" | "error";
type ID = string | number;
// Intersection 타입: 두 타입 합치기
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // { name: string; age: number }
Interface vs Type
// 대부분의 경우 동일하게 사용 가능
// Interface: 객체 구조, 확장 가능 (extends)
interface Animal { name: string }
interface Dog extends Animal { breed: string }
// Type: Union/Intersection 등 복잡한 타입 표현
type StringOrNumber = string | number;
type Result<T> = { data: T } | { error: string };
React에서 권장: Props/상태 등 객체 타입은
interface, Union/복잡한 타입은type사용
제네릭 (Generics)
타입을 변수처럼 사용해 재사용 가능한 코드를 만듭니다.
// 제네릭 함수: T는 호출 시 결정됨
function identity<T>(value: T): T {
return value;
}
identity<string>("hello"); // T = string
identity<number>(42); // T = number
identity("hello"); // T 자동 추론 → string
// 배열 첫 번째 요소 반환
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first([1, 2, 3]); // number | undefined
first(["a", "b"]); // string | undefined
// 제네릭 인터페이스
interface ApiResponse<T> {
data: T;
success: boolean;
message: string;
}
// 사용
const response: ApiResponse<User[]> = {
data: [{ id: 1, name: "Alice", email: "a@b.com", createdAt: new Date() }],
success: true,
message: "조회 성공",
};
// React useState와 제네릭
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
const [count, setCount] = useState<number>(0); // 초기값으로 자동 추론 가능
타입 좁히기 (Type Narrowing)
Union 타입에서 실제 타입을 확인한 후 사용합니다.
function process(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // string 메서드 사용 가능
} else {
console.log(value.toFixed(2)); // number 메서드 사용 가능
}
}
// instanceof로 클래스 구분
function handleError(error: unknown) {
if (error instanceof Error) {
console.log(error.message); // Error 타입 보장
} else {
console.log("알 수 없는 오류");
}
}
유틸리티 타입
자주 쓰는 TypeScript 내장 유틸리티 타입입니다.
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Partial: 모든 속성을 선택적으로
type PartialUser = Partial<User>;
// → { id?: number; name?: string; email?: string; password?: string }
// Required: 모든 속성을 필수로
type RequiredUser = Required<PartialUser>;
// Pick: 특정 속성만 선택
type PublicUser = Pick<User, "id" | "name" | "email">;
// → { id: number; name: string; email: string }
// Omit: 특정 속성만 제외
type UserWithoutPassword = Omit<User, "password">;
// → { id: number; name: string; email: string }
// Record: 키-값 타입 정의
type UserMap = Record<string, User>;
// → { [key: string]: User }
// ReturnType: 함수 반환 타입 추출
const fetchUser = async () => ({ id: 1, name: "Alice" });
type FetchResult = Awaited<ReturnType<typeof fetchUser>>;
// → { id: number; name: string }
React에서 자주 쓰는 타입
import { ReactNode, MouseEvent, ChangeEvent, FormEvent } from "react";
// children prop 타입
interface Props {
children: ReactNode; // JSX, 문자열, 숫자, 배열 모두 허용
}
// 이벤트 핸들러 타입
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget.innerText);
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
// useState에서 null 가능한 타입
const [user, setUser] = useState<User | null>(null);
// useRef DOM 타입
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);