React + TypeScript에서는 Props, State, Event, Ref 등에 타입을 지정 해서 컴포넌트의 안전성과 자동 완성을 확보합니다.

Props 타이핑

TYPESCRIPT
// Props 타입 정의
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
}

// 함수 컴포넌트에 Props 적용
function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) {
  return (
    <button
      className={`btn-${variant}`}
      onClick={onClick}
      disabled={disabled}
    >
      {label}
    </button>
  );
}

children Props

TYPESCRIPT
import { ReactNode } from 'react';

interface CardProps {
  title: string;
  children: ReactNode;  // JSX, 문자열, 숫자, 배열, null 등
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div>{children}</div>
    </div>
  );
}

제네릭 컴포넌트

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

// 사용 — T가 자동 추론됨
<List
  items={[{ id: 1, name: '홍길동' }]}
  renderItem={(user) => <span>{user.name}</span>}
  keyExtractor={(user) => String(user.id)}
/>

Hooks 타이핑

useState

TYPESCRIPT
// 초기값에서 타입 추론
const [count, setCount] = useState(0); // number

// 타입 명시가 필요한 경우
const [user, setUser] = useState<User | null>(null);
// 초기값이 null이므로 타입을 명시해야 함

// 유니온 타입
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');

useRef

TYPESCRIPT
// DOM 요소 참조
const inputRef = useRef<HTMLInputElement>(null);

function focusInput() {
  inputRef.current?.focus(); // HTMLInputElement | null
}

// 값 저장용 (mutable ref)
const intervalRef = useRef<number | null>(null);

function startTimer() {
  intervalRef.current = window.setInterval(() => {}, 1000);
}

useReducer

TYPESCRIPT
type State = { count: number; error: string | null };

type Action =
  | { type: 'INCREMENT'; payload: number }
  | { type: 'DECREMENT'; payload: number }
  | { type: 'ERROR'; message: string };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + action.payload };
    case 'DECREMENT':
      return { ...state, count: state.count - action.payload };
    case 'ERROR':
      return { ...state, error: action.message };
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, error: null });
dispatch({ type: 'INCREMENT', payload: 1 }); // 타입 안전

이벤트 핸들러 타이핑

TYPESCRIPT
import { ChangeEvent, FormEvent, MouseEvent } from 'react';

function Form() {
  // input change 이벤트
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  // form submit 이벤트
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  };

  // button click 이벤트
  const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
    console.log(e.clientX, e.clientY);
  };

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

인라인 이벤트 핸들러

TYPESCRIPT
// 인라인에서는 타입이 자동 추론됨
<input onChange={(e) => console.log(e.target.value)} />
// e: ChangeEvent<HTMLInputElement> — 자동 추론

// 별도 함수로 빼면 타입 명시 필요
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

HTML 속성 확장

TYPESCRIPT
import { ComponentPropsWithoutRef, ComponentPropsWithRef } from 'react';

// HTML button의 모든 속성을 상속
interface CustomButtonProps extends ComponentPropsWithoutRef<'button'> {
  variant?: 'primary' | 'secondary';
}

function CustomButton({ variant = 'primary', children, ...rest }: CustomButtonProps) {
  return (
    <button className={`btn-${variant}`} {...rest}>
      {children}
    </button>
  );
}

// 모든 button 속성 사용 가능
<CustomButton onClick={() => {}} disabled type="submit" variant="primary">
  클릭
</CustomButton>

Context 타이핑

TYPESCRIPT
import { createContext, useContext } from 'react';

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

// 안전한 커스텀 훅
function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth는 AuthProvider 안에서 사용해야 합니다');
  }
  return context;
}

정리

  • Props는 interfacetype으로 정의하고, childrenReactNode를 사용한다
  • useState는 초기값에서 추론하되, null 가능성이 있으면 타입을 명시한다
  • 이벤트 핸들러는 ChangeEvent, FormEvent, MouseEvent 등으로 타이핑한다
  • ComponentPropsWithoutRef로 HTML 요소의 속성을 상속받을 수 있다
  • Context는 null 체크를 포함한 커스텀 훅으로 감싸는 것이 안전하다
댓글 로딩 중...