error.message, unknown

강연주·2025년 5월 15일

📚 TIL

목록 보기
161/186

⚠️ error.message 'error는 unknown 형식입니다'

TypeScript에서 catch 블록의 error 변수는
기본적으로 unknown 타입으로 처리되기 때문에 발생한 오류.
TypeScript의 타입 안전성을 위해 error.message에 직접 접근할 수 없다.

🖥️ MeetupForm.tsx

    try {
      await createMutation.mutateAsync(meetupFormData);
      queryClient.invalidateQueries({ queryKey: ["meetups"] });
      queryClient.invalidateQueries({ queryKey: ["headhuntings"] });
      alert("모임 생성에 성공했습니다!");
      router.push("/");
    } catch (error: any) {
      console.error("모임 생성 오류 발생:", error?.message || "알 수 없는 오류");
      alert(`모임 생성 중 오류가 발생했습니다: ${error?.message || "알 수 없는 오류"}`);
    } finally {
      setIsSubmitting(false);
    }
  };
  

➡️ 1. error 타입 지정
catch (error: any)로 타입을 명시적으로 지정한다. TypeScript에서는 catch된 오류의 타입이 기본적으로 unknown이기 때문에, any로 지정하여 타입 체크를 우회.

➡️ 2. 안전한 접근

error?.message || "알 수 없는 오류" 구문을 사용

  • 옵셔널 체이닝(?.)으로 error가 null 또는 undefined가 아닐 때만 message 속성에 접근
  • 대체 메시지("알 수 없는 오류")를 제공해 message 속성이 없을 경우에도 안전하게 처리

그런데 영 아리까리해서 더 알아보는 게 좋겠다.


catch 블록에서만 error 변수를 unknown 타입으로 처리하는가?

➡️ YES. TypeScript 2.0 이후부터 catch 블록의 error 변수가 unknown 타입으로 처리된다.

이유는?

타입 안정성을 높이기 위해서.
JavaScript에서는 어떤 타입의 에러든 throw될 수 있으므로, 실제로는 예상치 못한 타입도 들어올 수 있다.

직접 접근이 불가능한 이유는?

  • unknown 타입은 TS에서 가장 안전한 타입 중 하나
  • unknown 타입의 변수에는 타입 검사/변환 없이 직접 속성에 접근 불가능
  • 즉, error.message와 같은 속성에 직접 접근하려면 먼저 타입 검사 필요

JavaScript와 비교

  • JS에서는 타입 검사 없이 error.message에 직접 접근 가능
  • TS에서는 타입 안정성 보장 목적으로 이런 접근 제한

🦛 Error 타입이란?

JS 내장 객체로 실행 시간에 발생하는 오류를 나타낸다.
TS에서는 이를 인터페이스로 정의하고 있다.

   🖥️ typescript

   interface Error {
       name: string;
       message: string;
       stack?: string;
   }
  • Error 타입 주요 속성

    • name : 오류 이름 (예: "TypeError", "SyntaxError" 등)
    • message : 오류 메시지 (개발자가 읽을 수 있는 오류 설명)
    • stack : 스택 트레이스 (선택적 속성, 오류가 발생한 위치 추적)

💚 해결 방법들

1. 타입 단언 사용 (Type Assertion)

🖥️ catch (error: unknown) {
  const err = error as Error;
  console.log(err.message);
}
  • 타입 단언이란?

    TS에서 컴파일러에게, 내가 이 변수의 타입을 알고 있으니까 내가 말하는 타입으로 처리해달라고 지시하는 것. 두 가지 문법이 있다.
    1. 꺾쇠 괄호 문법 (<타입>변수)
    2. as 문법 (변수 as 타입) : JSX와 호환되어 더 많이 사용됨
🖥️ typescript

try {
  // 오류 발생 가능한 코드
} catch (error: unknown) {
  // 방법 1: as 키워드 사용
  const err = error as Error;
  console.log(err.message); // 이제 Error 타입의 속성에 안전하게 접근 가능
  
  // 방법 2: 직접 타입 단언
  console.log((error as Error).message);
}
  • 타입 단언의 위험성

    타입 단언은 컴파일러의 타입 검사를 우회하는 것이므로 주의해서 사용해야 한다.
🖥️ typescript

try {
  throw "문자열 에러"; // 문자열을 throw할 수도 있음
} catch (error: unknown) {
  // 이 단언은 런타임 오류를 발생시킬 수 있음
  console.log((error as Error).message); // 런타임에 undefined 출력될 수 있음
}
  • 안전한 타입 단언 패턴

    타입 단언을 사용할 때는 먼저 해당 타입인지 확인하는 것이 좋다.
🖥️ typescript

try {
  // 오류 발생 가능한 코드
} catch (error: unknown) {
  // 먼저 타입 체크 후 단언
  if (typeof error === 'object' && error !== null && 'message' in error) {
    const err = error as Error;
    console.log(err.message);
  } else {
    console.log('알 수 없는 오류 발생');
  }
}
  • 커스텀 에러 타입과 단언

    커스텀 에러 클래스를 사용하는 경우
🖥️ typescript

class ApiError extends Error {
  statusCode: number;
  
  constructor(message: string, statusCode: number) {
    super(message);
    this.name = 'ApiError';
    this.statusCode = statusCode;
  }
}

try {
  throw new ApiError('API 요청 실패', 404);
} catch (error: unknown) {
  // ApiError로 타입 단언
  const apiError = error as ApiError;
  console.log(apiError.statusCode); // 404
}

2. 타입 가드 사용 (Type Guard)

🖥️ catch (error: unknown) {
  if (error instanceof Error) {
    console.log(error.message);
  }
}
  • 타입 가드란?

    타입 가드는 특정 스코프 내에서 변수의 타입을 보장하는 런타임 검사다.
    타입 가드를 사용시 TS는 해당 스코프 내에서 변수를 더 구체적인 타입으로 좁힐 수 있다.

  • instanceof 연산자

    instancof는 JS연산자로, 객체가 특정 클래스의 인스턴스인지 확인한다.
    이는 클래스 계층 구조를 확인할 수 있어 유용하다.
    (객체 instanceof 생성자)

  • instanceof의 장점

  1. 타입 안정성 : 런타임에서 실제로 해당 클래스의 인스턴스인지 확인하므로 타입 단언보다 안전하다.
  2. 상속 관계 확인 : 클래스 계층 구조에서 상속 관계를 확인할 수 있다.
  3. 코드 가독성 : 타입 검사 의도가 명확하게 표현된다.
  • Error와 함께 instanceof 사용하기

🖥️ typescript

try {
  // 오류 발생 가능한 코드
} catch (error: unknown) {
  if (error instanceof Error) {
    // 이 블록 내에서 TypeScript는 error를 Error 타입으로 인식
    console.log(error.message); // 안전하게 접근 가능
    console.log(error.stack);   // 안전하게 접근 가능
  } else {
    // error가 Error 인스턴스가 아닌 경우
    console.log('알 수 없는 오류:', error);
  }
}
  • 타양한 오류 타입 처리하기

🖥️ typescript

try {
  // 오류 발생 가능한 코드
} catch (error: unknown) {
  if (error instanceof TypeError) {
    console.log('타입 오류:', error.message);
  } else if (error instanceof SyntaxError) {
    console.log('구문 오류:', error.message);
  } else if (error instanceof ReferenceError) {
    console.log('참조 오류:', error.message);
  } else if (error instanceof Error) {
    console.log('일반 오류:', error.message);
  } else {
    console.log('알 수 없는 오류:', error);
  }
}
  • 커스텀 에러 클래스와 instanceof

    커스텀 에러 클래스를 사용할 때 instanceof가 특히 유용하다
🖥️ typescript

class NetworkError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ValidationError extends Error {
  fields: string[];
  
  constructor(message: string, fields: string[]) {
    super(message);
    this.name = 'ValidationError';
    this.fields = fields;
  }
}

try {
  const random = Math.random();
  if (random < 0.33) {
    throw new NetworkError('네트워크 연결 실패');
  } else if (random < 0.66) {
    throw new ValidationError('유효성 검사 실패', ['email', 'password']);
  } else {
    throw new Error('일반 오류');
  }
} catch (error: unknown) {
  if (error instanceof NetworkError) {
    console.log('네트워크 오류:', error.message);
    // 네트워크 재연결 로직
  } else if (error instanceof ValidationError) {
    console.log('유효성 검사 오류:', error.message);
    console.log('문제 필드:', error.fields);
    // 유효성 오류 처리 로직
  } else if (error instanceof Error) {
    console.log('일반 오류:', error.message);
  } else {
    console.log('알 수 없는 오류:', error);
  }
}
  • 사용자 정의 타입 가드

    instanceof와 함께 사용자 정의 타입 가드 함수를 만들 수 있다.
🖥️ typescript

// 타입 서술 함수
function isNetworkError(error: unknown): error is NetworkError {
  return error instanceof NetworkError;
}

function isValidationError(error: unknown): error is ValidationError {
  return error instanceof ValidationError;
}

try {
  // 오류 발생 가능한 코드
} catch (error: unknown) {
  if (isNetworkError(error)) {
    // 여기서 error는 NetworkError 타입
    console.log(error.message);
  } else if (isValidationError(error)) {
    // 여기서 error는 ValidationError 타입
    console.log(error.fields);
  }
}
  • 실전 에러 처리 패턴

    실제 애플리케이션에서는 다음과 같은 패턴을 자주 사용한다.
🖥️ typescript

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new ApiError(`API 오류: ${response.status}`, response.status);
    }
    return await response.json();
  } catch (error: unknown) {
    if (error instanceof ApiError) {
      if (error.statusCode === 401) {
        // 인증 오류 처리
        redirectToLogin();
      } else if (error.statusCode === 404) {
        // 리소스 없음 처리
        showNotFoundMessage();
      } else {
        // 기타 API 오류
        showErrorMessage(error.message);
      }
    } else if (error instanceof Error) {
      // 네트워크 오류 등 일반 오류
      showErrorMessage(`요청 실패: ${error.message}`);
    } else {
      // 알 수 없는 오류
      showErrorMessage('알 수 없는 오류가 발생했습니다');
    }
    // 오류를 다시 throw하여 호출자에게 전파할 수 있음
    throw error;
  }
}

3. any 타입 사용 : 안정성 저하

🖥️ catch (error: any) {
  console.log(error.message);
}

4. 옵셔널 체이닝과 타입 단언 함께 사용

🖥️ catch (error: unknown) {
  console.log((error as Error)?.message || "알 수 없는 오류");
}
profile
아무튼, 개발자

0개의 댓글