plogger

React + TypeScript 프로젝트 설정 및 실전 타입 패턴

프로젝트 생성

npm create vite@latest my-app -- --template react-ts

컴포넌트 Props 타입 정의

// interface 방식 (권장)
interface ButtonProps {
  label: string
  variant?: 'primary' | 'secondary' | 'danger'
  disabled?: boolean
  onClick: () => void
}

function Button({ label, variant = 'primary', disabled = false, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      disabled={disabled}
      onClick={onClick}
    >
      {label}
    </button>
  )
}

children 타입

import { ReactNode, FC } from 'react'

interface CardProps {
  title: string
  children: ReactNode
}

const Card: FC<CardProps> = ({ title, children }) => (
  <div className="card">
    <h2>{title}</h2>
    {children}
  </div>
)

이벤트 핸들러 타입

function Form() {
  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const form = e.currentTarget
    const data = new FormData(form)
  }

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    console.log(e.target.value)
  }

  function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
    console.log(e.currentTarget.id)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} />
      <button id="submit" onClick={handleClick}>제출</button>
    </form>
  )
}

useState 타입

const [count, setCount] = useState<number>(0)
const [user, setUser] = useState<User | null>(null)
const [items, setItems] = useState<string[]>([])

// 초기값으로 타입 추론 가능한 경우 생략 가능
const [name, setName] = useState('') // string으로 자동 추론

useRef 타입

// DOM 참조
const inputRef = useRef<HTMLInputElement>(null)

// 값 저장 (렌더링 없이)
const timerRef = useRef<ReturnType<typeof setTimeout>>(null)

제네릭 컴포넌트

interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => ReactNode
  keyExtractor: (item: T) => string
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map(item => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

// 사용
<List
  items={users}
  renderItem={user => <span>{user.name}</span>}
  keyExtractor={user => user.id}
/>

API 응답 타입

// types/api.ts
export interface ApiResponse<T> {
  data: T
  message: string
  success: boolean
}

export interface User {
  id: number
  name: string
  email: string
}

// 사용
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
}