TypeScript 5.7 타입 안정성 극대화: satisfies와 const 타입 파라미터 실전
TypeScript 5.7의 satisfies 연산자, const 타입 파라미터, using 리소스 관리를 활용해 런타임 버그를 컴파일 타임에 잡는 실전 패턴을 정리한다. tsconfig 설정과 성능 팁도 함께 다룬다.
TypeScript 5.7 타입 안정성 극대화: satisfies와 const 타입 파라미터 실전
타입스크립트는 이제 자바스크립트 생태계의 기본 언어로 자리 잡았다. 2024년 11월 릴리스된 5.7을 지나며 언어의 표현력은 한층 정교해졌고, 단순히 any를 줄이는 수준을 넘어 타입 시스템으로 도메인 규칙 자체를 인코딩하는 단계로 진화했다. 이 글은 실무에서 자주 놓치는 최신 문법을 중심으로, 런타임에서 터질 버그를 컴파일 타임에 붙잡는 구체적 기법을 다룬다.
satisfies로 추론과 검증을 동시에
satisfies 연산자는 5.0에서 등장했지만 여전히 저평가된 기능이다. 타입 애너테이션을 붙이면 변수가 그 타입으로 좁혀지면서 원래의 리터럴 정보가 사라진다. 반면 satisfies는 값이 특정 타입을 만족하는지 검증하면서도, 실제 추론된 좁은 타입을 그대로 유지한다. 예컨대 라우트 설정 객체를 정의할 때, 각 키가 유효한 경로인지 검사하면서도 개별 값의 구체적 타입을 잃지 않는다.
const config = {
port: 3000,
host: 'localhost',
routes: ['/api', '/health'],
} satisfies Record<string, number | string | string[]>;
// config.routes는 string[]로 정확히 추론된다
이 패턴은 색상 팔레트, 환경 변수 매핑, 상태 머신 정의처럼 키-값 구조가 고정된 설정에서 특히 강력하다. as로 타입을 강제하던 관행을 satisfies로 대체하면, 오탈자나 잘못된 값을 즉시 잡아내면서도 자동 완성의 정확도가 유지된다.
const 타입 파라미터와 정밀한 추론
5.0부터 제네릭 함수의 타입 파라미터에 const 수식어를 붙일 수 있다. 이를 통해 인자로 넘긴 배열이나 객체가 넓은 타입으로 추론되는 대신, 리터럴 튜플로 좁게 유지된다. 라이브러리 API를 설계할 때 호출자가 as const를 매번 붙이지 않아도 정밀한 추론이 자동으로 이뤄지므로, 개발자 경험이 크게 개선된다.
여기에 5.4의 NoInfer 유틸리티를 결합하면 특정 타입 파라미터가 특정 인자에서 추론되지 않도록 막을 수 있다. 기본값과 입력값이 섞여 원치 않는 유니온 타입이 만들어지는 문제를 깔끔하게 해결한다. 또한 5.5에서 추가된 추론된 타입 프레디킷 덕분에 filter 같은 배열 메서드가 반환 타입을 자동으로 좁혀, 이전에 수동으로 작성하던 타입 가드 함수가 상당수 불필요해졌다. 이런 기능들은 개별로는 사소해 보이지만, 누적되면 코드베이스 전체의 as 캐스팅과 non-null 단언을 눈에 띄게 줄여준다.
using 선언과 tsconfig 최적화
5.2에서 도입된 using과 await using 선언은 명시적 리소스 관리를 언어 차원에서 지원한다. 파일 핸들, 데이터베이스 커넥션, 락처럼 사용 후 반드시 해제해야 하는 자원을 Symbol.dispose 규약으로 정의하면, 스코프를 벗어날 때 자동으로 정리된다. try-finally로 정리 로직을 반복 작성하던 패턴이 사라지고, 자원 누수 가능성이 구조적으로 줄어든다.
컴파일 성능 측면에서는 5.6의 옵션과 프로젝트 참조 구성을 점검할 필요가 있다. 대규모 모노레포에서는 isolatedModules와 verbatimModuleSyntax를 켜서 트랜스파일러 호환성을 확보하고, incremental 빌드와 tsc의 build 모드를 활용해 전체 타입 체크 시간을 관리한다. strict 계열 옵션은 반드시 전부 활성화하되, noUncheckedIndexedAccess를 추가로 켜면 배열과 객체 인덱싱 시 undefined 가능성을 강제로 다루게 되어 실전 버그의 상당수를 예방할 수 있다. 타입 커버리지를 지표로 관리하며 any 사용을 점진적으로 줄이는 것이 장기적으로 유지보수 비용을 낮추는 가장 확실한 길이다.
도메인 규칙을 타입으로 인코딩하기
성숙한 타입스크립트 코드베이스는 타입 시스템을 단순한 안전망이 아니라 도메인 규칙의 표현 수단으로 활용한다. 템플릿 리터럴 타입을 쓰면 특정 접두사로 시작하는 문자열이나 정해진 형식의 식별자를 타입 차원에서 강제할 수 있다. 예컨대 이메일 형식이나 라우트 경로의 패턴을 타입으로 제약하면, 잘못된 값이 함수에 전달되는 순간 컴파일러가 즉시 거부한다. 이는 런타임 유효성 검사에만 의존하던 방어를 컴파일 타임으로 앞당기는 효과가 있다.
브랜디드 타입은 구조적으로는 같지만 의미가 다른 값을 구분하는 강력한 기법이다. 사용자 아이디와 상품 아이디가 모두 숫자라 해도, 브랜딩을 통해 서로 섞이지 않도록 막으면 인자 순서를 바꿔 넘기는 실수를 원천 차단한다. 판별 유니온은 상태 머신이나 API 응답처럼 여러 형태를 가지는 데이터를 다룰 때 특히 유용하다. 공통 판별 필드를 기준으로 각 케이스를 좁혀나가면, 처리하지 않은 상태가 남았을 때 컴파일러가 이를 지적해 준다.
이런 기법들을 실전에 적용하려면 런타임 검증과의 연계가 중요하다. 외부에서 들어오는 데이터는 타입만으로는 신뢰할 수 없으므로, 스키마 검증 라이브러리로 경계에서 파싱하고 그 결과에서 타입을 추론하는 패턴이 널리 쓰인다. 이렇게 하면 검증 로직과 타입 정의가 하나의 소스에서 파생되어, 둘이 어긋나는 문제를 구조적으로 없앨 수 있다. 타입과 런타임 검증을 일치시키는 이 습관이 견고한 애플리케이션의 토대가 된다.
정교한 타입은 협업하는 팀에게 살아 있는 문서 역할도 한다. 함수의 시그니처만 봐도 어떤 값을 넣어야 하고 무엇이 돌아오는지 명확히 드러나므로, 별도의 설명 없이도 의도가 전달된다. 새로 합류한 동료가 낯선 코드를 다룰 때, 잘 다듬어진 타입은 자동 완성과 즉각적인 오류 표시를 통해 안전한 길을 안내한다. 반대로 타입을 대충 넓게 두면 이런 이점이 사라지고, 실수가 런타임까지 흘러가 발견이 늦어진다. 좋은 타입 설계는 결국 팀 전체의 생산성과 코드 신뢰도를 함께 끌어올리는 투자다.
한편 지나친 타입 기교는 경계해야 한다. 복잡한 조건부 타입과 재귀 타입을 남발하면 컴파일러가 이해하기 어려운 오류 메시지를 쏟아내고, 동료가 코드를 읽는 데 오히려 부담을 준다. 표현력과 가독성 사이의 균형을 잡는 판단이 성숙한 개발자의 역량이다. 정말 필요한 곳에만 고급 기법을 쓰고, 대부분의 코드는 단순하고 명확한 타입으로 유지하는 절제가 장기적으로 유지보수하기 좋은 코드베이스를 만든다.