4 분 소요

개요

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);


관련 링크