← 전체 글로 돌아가기

Next.js

React 컴포넌트에서 타입 단언이 자꾸 늘어날 때

Props 타입을 제대로 정의해서 타입 단언의 악순환을 벗어나는 방법.

React 프로젝트가 커지면서 자꾸만 as 타입 단언이 늘어난다. 처음에는 한두 개 정도로 시작했지만, 어느 순간 코드 곳곳에 as anyas unknown이 널려있다면 뭔가 잘못된 신호다.

타입 단언이 늘어나는 이유

대부분 Props 타입이 제대로 정의되지 않았기 때문이다.

// ❌ 나쁜 예
function UserCard({ user }: any) {
  return <div>{user.name}</div>; // user가 any라서 그냥 쓸 수 있음
}

// 나중에 이걸 쓸 때
<UserCard user={someValue as User} /> // 타입 단언 필요

최근 React와 TypeScript의 변화

React 18.2부터 React.FC를 안 쓰는 게 권장되고 있다. Props를 명확하게 정의해야 한다.

// ✅ 좋은 예
interface UserCardProps {
  user: User;
  onClick?: (id: string) => void;
}

function UserCard({ user, onClick }: UserCardProps) {
  return (
    <div onClick={() => onClick?.(user.id)}>
      {user.name}
    </div>
  );
}

부모에서 Props를 정의할 때

interface ParentProps {
  users: User[];
  onSelect?: (user: User) => void;
}

function Parent({ users, onSelect }: ParentProps) {
  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user} // ✅ 타입 단언 없음
          onClick={onSelect}
        />
      ))}
    </div>
  );
}

모바일 화면에서 특히 주의할 점

반응형 디자인에서 Props가 조건부로 전달될 때가 있다.

interface ResponsiveProps {
  isMobile: boolean;
  content: string;
  action?: (id: string) => void; // 필수가 아님을 명시
}

function Responsive({ isMobile, content, action }: ResponsiveProps) {
  return isMobile ? (
    <MobileView content={content} /> // action 생략 가능
  ) : (
    <DesktopView content={content} action={action} />
  );
}

이벤트 핸들러는 더 신경써야 한다

// ❌ 위험한 패턴
function Form() {
  const handleChange = (e: any) => {
    // e를 어떻게 써야 할지 모름
  };
}

// ✅ 안전한 패턴
function Form() {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.currentTarget.value; // 명확함
  };

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    // 마찬가지로 명확
  };

  return (
    <>
      <input onChange={handleChange} />
      <button onClick={handleClick}>Submit</button>
    </>
  );
}

빌드 타임에 확인하기

npm run build

타입 에러가 있으면 빌드 실패하도록 설정해두고, 타입 단언이 필요한 경우는 주석으로 이유를 남겨라.

// @ts-expect-error: API 응답이 항상 이 구조를 보장하지 않음
const data = response.data as ApiResponse;

언제 타입 단언은 괜찮은가?

  1. 외부 라이브러리 타입이 불완전할 때
  2. API 응답을 파싱한 직후
  3. DOM 쿼리 결과 (querySelector의 반환값)

이런 경우들은 단언이 실제로 필요하고 정당하다. 하지만 컴포넌트 내부에서 Props로 인한 단언은 Props 타입을 제대로 정의해서 없애야 한다.

결국 "이 Props는 정확히 뭔가?"를 명확히 하는 게 문제 해결의 시작이다.