TypeScript 타입 오류는 단순한 경고가 아니다
바이브 코딩으로 프로젝트를 만들다 보면 편집기 곳곳에 빨간 밑줄이 표시되거나 운영용 빌드 과정에서 TypeScript 타입 오류가 발생할 수 있다.
이때 AI에게 “오류가 보이지 않게 고쳐줘”라고 요청하면 any, 강제 타입 변환, 검사 제외 설정을 사용해 오류 표시만 없애는 경우가 있다. 화면이 정상적으로 보이면 문제가 해결됐다고 생각하기 쉽지만 실제 데이터와 코드가 기대하는 데이터가 일치하지 않는 상태는 그대로 남아 있을 수 있다.
TypeScript 타입 오류는 코드가 기대하는 값과 실제로 전달될 수 있는 값 사이에 차이가 있다는 신호다. 오류를 숨기는 것보다 왜 그런 차이가 발생했는지 확인하는 것이 먼저다.
TypeScript는 어떤 역할을 할까?
JavaScript에서는 변수에 문자열을 넣었다가 나중에 숫자를 넣어도 코드 작성 단계에서 오류가 발생하지 않는다. 잘못된 값이 전달돼도 실제 사용자가 해당 기능을 실행하기 전까지 문제를 발견하지 못할 수 있다.
TypeScript는 코드가 실행되기 전에 값의 형태를 검사한다. 함수가 문자열을 받아야 하는데 숫자가 전달되거나, 반드시 존재해야 하는 객체가 undefined일 가능성이 있다면 이를 오류로 알려준다.
TypeScript 공식 문서는 가능한 경우 새로운 프로젝트에서 엄격한 타입 검사를 활성화할 것을 권장한다. 특히 noImplicitAny와 strictNullChecks는 암시적인 any와 처리되지 않은 null, undefined를 찾는 데 중요한 옵션이다. TypeScript 엄격한 타입 검사
화면이 작동해도 타입 오류를 수정해야 하는 이유
타입 오류가 있는 코드도 특정 데이터에서는 정상적으로 작동할 수 있다. 예를 들어 사용자 이름이 항상 존재하는 테스트 계정에서는 문제가 없지만 이름이 없는 실제 사용자가 접속하면 오류가 발생할 수 있다.
개발 환경의 데이터는 개발자가 예상한 형태로 준비돼 있는 경우가 많다. 실제 서비스에는 빈 값, 오래된 데이터, 잘못된 API 응답과 예상하지 못한 사용자 입력이 들어올 수 있다.
타입 오류는 이런 예외 상황이 아직 처리되지 않았다는 사실을 알려주는 경우가 많다. 지금 당장 화면이 작동한다는 이유만으로 무시하면 운영 환경에서만 발생하는 오류로 이어질 수 있다.
타입 오류가 알려주는 대표적인 문제
TypeScript 타입 오류는 다음과 같은 문제를 발견하는 데 도움이 된다.
- API 응답과 화면 코드가 기대하는 데이터 구조의 차이
- 문자열과 숫자 형식의 불일치
- 존재하지 않을 수 있는 객체 접근
- 필수 속성 누락
- 잘못된 함수 인수
- 함수 반환값의 형식 오류
- 라이브러리 사용 방법과 설치 버전의 차이
- 잘못된 이벤트 객체 처리
- 비동기 함수 반환값 누락
오류 메시지가 복잡해 보여도 대부분은 “현재 값의 타입”과 “필요한 타입”이 다르다는 내용을 설명한다.
TypeScript 오류 메시지를 읽는 가장 쉬운 방법
타입 오류가 발생하면 메시지 전체를 한꺼번에 이해하려고 하지 말고 다음 네 가지를 찾는다.
- 오류가 발생한 파일과 줄 번호
- 현재 전달된 타입
- 코드가 요구하는 타입
- 두 타입이 달라진 원인
예를 들어 다음과 같은 메시지가 있다고 가정해 보자.
Type 'string | undefined' is not assignable to type 'string'.
이 메시지는 현재 값이 문자열일 수도 있지만 undefined일 수도 있다는 뜻이다. 반면 값을 받는 위치에서는 반드시 문자열이 필요하다.
이때 문자열이라고 강제로 선언하기 전에 왜 undefined가 될 수 있는지 확인해야 한다.
null과 undefined 오류를 무시하면 위험한 이유
데이터베이스 조회 결과가 없거나 API 응답이 늦으면 객체가 null 또는 undefined가 될 수 있다. 해당 가능성을 처리하지 않고 속성에 접근하면 실행 중 오류가 발생한다.
예를 들어 다음 코드는 사용자를 찾지 못했을 때 문제가 된다.
const user = await getUser()
return user.name
getUser()가 사용자를 찾지 못해 null을 반환할 수 있다면 먼저 이를 처리해야 한다.
const user = await getUser()
if (!user) {
return "사용자를 찾을 수 없습니다."
}
return user.name
strictNullChecks는 null과 undefined가 일반적인 값에 자동으로 포함되지 않도록 검사한다. 이를 통해 존재하지 않을 수 있는 값을 코드에서 명시적으로 처리하게 만든다.
문자열과 숫자 타입이 다를 때 확인할 것
URL과 입력창에서 가져온 값은 화면상 숫자로 보여도 문자열일 수 있다. 데이터베이스의 숫자 필드나 계산 함수에 그대로 전달하면 타입 오류가 발생한다.
예를 들어 게시물 번호를 다음처럼 가져올 수 있다.
const postId = params.id
params.id가 문자열인데 함수는 숫자를 요구한다면 두 가지 가능성을 확인해야 한다.
첫 번째는 해당 함수가 실제로 숫자를 받아야 하는 경우다. 이때는 값이 올바른 숫자인지 검증한 뒤 변환한다.
const postId = Number(params.id)
if (!Number.isInteger(postId)) {
throw new Error("올바르지 않은 게시물 번호입니다.")
}
두 번째는 데이터베이스의 식별자가 원래 문자열인 경우다. 이때는 숫자로 변환하는 것이 아니라 함수의 타입 정의를 수정해야 한다.
타입 오류를 고칠 때는 변환부터 하지 말고 실제 데이터가 어떤 형태여야 하는지 먼저 결정해야 한다.
any로 변경하면 오류가 사라지는 이유
any는 TypeScript가 해당 값의 타입을 검사하지 않도록 만든다. 따라서 복잡한 오류가 발생했을 때 값을 any로 변경하면 대부분의 빨간 밑줄이 사라진다.
하지만 오류가 해결된 것이 아니라 검사 기능을 제거한 것이다. 문자열이라고 예상한 값에 객체가 들어와도 TypeScript가 알려주지 않는다.
TypeScript 공식 문서에서도 any는 타입 검사 오류를 발생시키지 않는 특별한 타입이며, 과도하게 사용하면 TypeScript를 사용하는 목적이 약해진다고 설명한다. TypeScript의 기본 타입
외부에서 들어오는 값의 형태를 모를 때는 any보다 unknown을 사용하는 편이 안전하다. unknown은 값의 형태를 확인하기 전까지 바로 사용할 수 없기 때문이다.
as를 이용한 강제 변환을 주의해야 한다
다음과 같은 코드는 실제 값을 변환하지 않는다.
const user = response as User
이 코드는 TypeScript에 “이 값은 User 타입이라고 믿어도 된다”고 알려줄 뿐이다. 서버가 잘못된 값을 반환해도 실행 중 데이터는 그대로다.
타입 단언을 사용하기 전에 API 응답이 실제로 해당 구조를 만족하는지 확인해야 한다. 외부 API, 사용자 입력과 저장소에서 읽어온 데이터는 실행 시점의 검증이 필요할 수 있다.
강제 변환은 개발자가 값의 형태를 확실히 알고 있지만 TypeScript가 이를 추론하지 못하는 제한된 상황에서만 사용하는 것이 좋다.
느낌표로 null 오류를 숨기지 않는다
TypeScript에서 값 뒤에 붙이는 느낌표는 해당 값이 null이나 undefined가 아니라고 강제로 선언한다.
return user!.name
이 문법은 사용자가 반드시 존재한다는 사실을 TypeScript에 전달하지만 실행 중 검사를 추가하지는 않는다.
실제로 사용자가 없으면 여전히 오류가 발생한다. 값의 존재를 확실히 보장할 수 없다면 조건문, 기본값 또는 오류 처리를 사용해야 한다.
ts-ignore와 검사 설정 비활성화를 주의한다
AI가 타입 오류를 빠르게 제거하기 위해 다음과 같은 방법을 제안할 수 있다.
@ts-ignore추가strict옵션 비활성화- 오류가 있는 파일을 검사 대상에서 제외
- 빌드 과정에서 TypeScript 검사 생략
- 타입 정의 전체를
any로 변경 - 라이브러리 검사를 무조건 건너뛰기
이런 설정은 특정 상황에서 필요할 수 있지만 오류의 원인을 확인하기 전에 사용해서는 안 된다. 한 파일의 오류를 해결하기 위해 프로젝트 전체의 검사 수준을 낮추면 다른 문제까지 발견하기 어려워진다.
타입 검사 설정을 변경했다면 Git Diff를 통해 어떤 옵션이 달라졌는지 반드시 확인해야 한다.
빌드가 성공했다고 타입 오류가 없는 것은 아니다
TypeScript는 설정에 따라 오류가 있어도 JavaScript 결과물을 생성할 수 있다. noEmitOnError는 오류가 보고됐을 때 JavaScript, 소스맵과 선언 파일 출력을 중단하는 옵션이다.
TypeScript 공식 문서에 따르면 이 옵션의 기본값은 false다. 따라서 프로젝트의 빌드 방식과 프레임워크 설정에 따라 결과 파일이 만들어졌다고 해서 타입 검사를 모두 통과했다고 단정할 수 없다. TypeScript noEmitOnError 설정
프로젝트에서 제공하는 타입 검사 명령을 확인하고 별도로 실행하는 것이 좋다. 일반적인 TypeScript 프로젝트에서는 다음 명령을 사용할 수 있다.
npx tsc --noEmit
프로젝트마다 설정이 다를 수 있으므로 package.json의 scripts도 함께 확인해야 한다.
TypeScript 타입 오류를 안전하게 수정하는 순서
타입 오류가 발생했다면 다음 순서로 접근하는 것이 좋다.
1. 같은 오류를 다시 재현한다
편집기의 빨간 밑줄만 보지 말고 프로젝트의 타입 검사 명령을 실행한다. 오류가 발생한 파일, 줄 번호와 오류 코드를 기록한다.
여러 오류가 있다면 가장 처음 발생한 오류부터 확인한다. 하나의 잘못된 타입 정의가 뒤쪽에서 여러 오류를 만들 수 있기 때문이다.
2. 현재 타입과 필요한 타입을 비교한다
오류 메시지에서 현재 값의 타입과 코드가 요구하는 타입을 찾는다.
예를 들어 현재 타입이 User | null이고 필요한 타입이 User라면 사용자를 찾지 못하는 상황이 처리되지 않은 것이다.
3. 값이 만들어지는 위치를 찾는다
오류가 표시된 줄만 수정하지 말고 해당 값이 어디에서 시작됐는지 따라간다.
API 응답, 데이터베이스 조회, URL 매개변수, 사용자 입력 또는 다른 함수의 반환값인지 확인한다. 타입 정의와 실제 데이터를 함께 비교해야 한다.
4. 실제 데이터가 잘못됐는지 타입 정의가 잘못됐는지 결정한다
코드가 문자열을 기대하지만 실제 데이터가 숫자라면 어느 쪽이 올바른 규칙인지 결정해야 한다.
실제 데이터가 잘못됐다면 값을 검증하고 변환한다. 타입 정의가 현실과 다르다면 인터페이스나 타입을 수정한다.
오류를 없애기 위해 실제 데이터와 다른 타입을 강제로 선언해서는 안 된다.
5. 조건문으로 타입을 좁힌다
TypeScript는 typeof, instanceof, 속성 존재 확인과 조건문을 이용해 값의 타입을 좁힐 수 있다.
function formatValue(value: string | number) {
if (typeof value === "number") {
return value.toLocaleString()
}
return value.trim()
}
TypeScript 공식 문서는 이러한 검사를 타입 가드라고 설명하며, 실행 흐름을 분석해 각 조건문 안에서 더 구체적인 타입으로 좁힌다. TypeScript 타입 좁히기
6. 수정 범위를 최소화한다
한 개의 타입 오류를 해결하기 위해 여러 인터페이스와 함수의 타입을 한꺼번에 바꾸지 않는 것이 좋다.
먼저 데이터가 생성되는 위치 또는 잘못 전달되는 경계를 수정한다. 공통 타입을 변경해야 한다면 해당 타입을 사용하는 모든 파일에 미치는 영향을 확인한다.
7. 타입 검사와 테스트를 다시 실행한다
오류가 사라졌다면 타입 검사, 테스트와 운영용 빌드를 실행한다.
타입 검사가 통과해도 기능이 정상이라는 보장은 없다. 타입은 맞지만 잘못된 값이 전달될 수 있으므로 실제 사용자 흐름도 함께 테스트해야 한다.
8. Git Diff로 검사 설정이 바뀌지 않았는지 확인한다
AI가 코드를 수정했다면 tsconfig.json, 빌드 설정과 패키지 스크립트가 함께 변경되지 않았는지 확인한다.
strict가 꺼지거나 타입 검사 명령이 삭제됐다면 오류가 해결된 것이 아니라 검사가 약해진 것일 수 있다.
AI에게 타입 오류 수정을 요청하는 프롬프트
다음 프롬프트를 사용하면 오류를 숨기는 방식의 수정을 줄이는 데 도움이 된다.
아래 TypeScript 타입 오류의 원인을 먼저 설명해 줘.
조건:
1. any, @ts-ignore와 불필요한 타입 단언을 사용하지 마라.
2. strict와 strictNullChecks를 끄지 마라.
3. 타입 검사 또는 빌드 검사를 생략하지 마라.
4. 현재 타입과 필요한 타입을 구분해 설명해 줘.
5. 값이 생성되는 위치를 추적해 실제 데이터 구조를 확인해 줘.
6. 실제 데이터와 타입 정의 중 무엇이 잘못됐는지 판단 근거를 제시해 줘.
7. 기존 기능에 영향을 주지 않는 최소 수정안을 작성해 줘.
8. 수정 후 실행할 타입 검사와 테스트를 알려줘.
먼저 분석 결과와 수정 계획만 제시하고 코드를 바로 변경하지 마라.
AI가 제시한 수정안에서도 any, as, 느낌표와 검사 제외 설정이 추가되지 않았는지 직접 확인해야 한다.
TypeScript가 모든 오류를 찾아주는 것은 아니다
타입 검사가 통과했다고 코드가 완전히 안전한 것은 아니다. TypeScript는 값의 형태를 중심으로 검사하며 비즈니스 규칙과 보안 권한까지 자동으로 판단하지 않는다.
예를 들어 사용자의 게시물 번호가 올바른 숫자라는 사실은 확인할 수 있지만 그 사용자가 해당 게시물을 수정할 권한이 있는지는 별도의 서버 코드와 테스트로 검증해야 한다.
따라서 타입 검사, 코드 리뷰, 자동화 테스트와 실제 기능 테스트를 함께 사용해야 한다.
마무리
TypeScript 타입 오류는 개발을 방해하는 불필요한 경고가 아니라 코드가 예상하는 값과 실제로 전달될 수 있는 값이 다르다는 신호다.
any, 강제 타입 변환, 느낌표와 검사 제외 설정으로 오류를 숨기면 빨간 밑줄은 사라질 수 있지만 실제 문제는 남아 있을 수 있다.
오류를 재현하고 현재 타입과 필요한 타입을 비교한 뒤 값이 만들어지는 위치를 추적해야 한다. 실제 데이터와 타입 정의 중 잘못된 부분을 수정하고 타입 좁히기와 명시적인 예외 처리를 사용해야 한다.
마지막으로 타입 검사, 테스트, 운영 빌드와 Git Diff를 다시 확인해야 한다. 이 과정을 반복하면 AI가 만든 TypeScript 코드도 더 안전하게 검증하고 수정할 수 있다.