strict 모드는 tsconfig.json의 "strict": true 옵션입니다. 암묵적 any, 검사하지 않은 null과 undefined, 안전하지 않은 함수 대입, 초기화되지 않은 클래스 필드를 잡아내는 여덟 가지 타입 검사 옵션을 한 번에 켜는 스위치입니다. TypeScript 7에서는 strict가 기본으로 켜져 있습니다. strict를 통과하는 코드는 다음과 같습니다.
출력은 no user, SyntaxError, 42입니다. : number나 ?., instanceof 검사를 지우면 파일이 더 이상 컴파일되지 않습니다.
strict 켜기
{
"compilerOptions": {
"strict": true
}
}
개별 옵션은 어느 방향으로든 strict보다 우선합니다. "strict": true, "strictNullChecks": false는 null 검사만 빼고 모두 유지하고, "strict": false, "noImplicitAny": true는 그 옵션 하나만 켭니다. 새로운 strict 계열 옵션은 이 묶음에 합류하므로, strict를 켜면 앞으로 추가될 검사도 받게 됩니다.
TypeScript의 strict는 런타임 모드인 JavaScript의 "use strict" 지시어와 아무 관계가 없습니다. TypeScript 7은 필요한 곳에 항상 "use strict"를 출력하며, alwaysStrict: false로 설정하면 TS5108 오류입니다(옵션이 제거됐습니다).
각 옵션이 잡아내는 것
| 옵션 | 보고하는 것 | 오류 |
|---|---|---|
noImplicitAny | 타입이 조용히 any가 될 매개변수나 변수 | TS7006 Parameter 'x' implicitly has an 'any' type. |
strictNullChecks | null이나 undefined일 수 있는 값을 사용 | TS18048 'u' is possibly 'undefined'. |
strictFunctionTypes | 매개변수 타입이 요구보다 좁은 함수를 대입 | TS2322 |
strictBindCallApply | .call, .bind, .apply에 잘못된 인수 | TS2345 |
strictPropertyInitialization | 한 번도 대입되지 않는 클래스 필드 | TS2564 Property 'name' has no initializer and is not definitely assigned in the constructor. |
noImplicitThis | 중첩된 function 안처럼 암묵적 any 타입인 this | TS2683 |
useUnknownInCatchVariables | catch 변수를 좁히기 전에 사용(타입이 unknown) | TS18046 'err' is of type 'unknown'. |
strictBuiltinIteratorReturn | 내장 이터레이터의 it.next().value를 항상 정의된 값으로 취급 | TS2322 |
코드를 가장 많이 바꾸는 두 옵션은 noImplicitAny와 strictNullChecks입니다. 다음 블록은 일부러 둘 다 위반합니다.
출력은 index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.와 index.ts(11,13): error TS18048: 'user' is possibly 'undefined'.입니다. strict가 없으면 이 코드는 컴파일된 뒤 실행 시점에 TypeError: Cannot read properties of undefined (reading 'name')로 멈춥니다. strict가 있으면 그 멈춤이 컴파일 오류가 됩니다. 수정한 코드는 이 페이지의 첫 번째 블록입니다.
strictFunctionTypes가 있는 이유
문자열만 처리하는 함수를 숫자가 들어올 수 있는 자리에 쓰면 안 됩니다. 아래 검사는 @ts-expect-error로 막아 두었으므로, 실행해서 이 오류가 무엇을 막는지 볼 수 있습니다.
주석이 없으면 이 대입은 TS2322 오류 Type '(s: string) => void' is not assignable to type 'Handler'.이고, 뒤이어 Types of parameters 's' and 'value' are incompatible.가 나옵니다. 설계상 남아 있는 예외가 하나 있습니다. 메서드 문법으로 선언한 메서드(인터페이스 안의 handle(value: string | number): void)의 매개변수는 여전히 느슨하게 검사되므로, 그런 메서드에는 shout를 오류 없이 대입할 수 있습니다.
strictPropertyInitialization
모든 클래스 필드는 선언이나 생성자에서 값을 받아야 합니다. 이를 만족시키는 방법은 세 가지입니다.
출력:
Account {
owner: 'Ada',
balance: 0,
history: [],
lastLogin: undefined,
sessionId: 's-1'
}
target이 ES2022일 때 클래스 필드는 실제 JavaScript 필드이므로, lastLogin과 sessionId 프로퍼티는 undefined 값으로 존재합니다. !는 런타임 보호를 전혀 추가하지 않고 검사만 없애므로 다른 형태를 우선하세요. 이 옵션은 strictNullChecks가 필요하며, 그것을 끄면 이 옵션도 꺼집니다.
기존 프로젝트에 strict 적용하기
큰 코드베이스를 한 번에 strict로 바꾸면 오류가 수백 개 나올 수 있습니다. strict는 TypeScript 7의 기본값이므로, tsconfig.json에 strict를 한 번도 쓰지 않은 프로젝트를 업그레이드하면 저절로 켜집니다. 마이그레이션하는 동안 예전 동작이 필요하면 "strict": false를 쓰세요. 빌드를 깨뜨리지 않는 방법은 다음과 같습니다.
"strict": true를 추가하고 오류가 가장 많은 옵션을 끕니다. 보통"strictNullChecks": false와"noImplicitAny": false입니다.- 남은 오류를 고친 뒤 옵션을 하나 더 켜고 반복합니다.
noImplicitAny는 대부분 매개변수에 타입을 표기해 고칩니다.strictNullChecks는 값이 없을 수 있는 곳에| undefined를 추가한 뒤?.,??,if검사로 처리합니다.- 수정을 미뤄야 하는 곳에는 이유와 함께
// @ts-expect-error를 붙입니다.@ts-ignore와 달리 문제가 사라지면 오류를 보고하므로 목록이 저절로 줄어듭니다.
오류를 한꺼번에 없애려고 as any나 !를 쓰지 마세요. 하나하나가 바로 그 옵션이 찾으려던 버그를 숨깁니다.
strict에 포함되지 않는 유용한 옵션
다음 옵션들은 올바른 코드도 자주 거부하기 때문에 따로 있습니다. 그래도 많은 프로젝트가 켭니다.
| 옵션 | 하는 일 |
|---|---|
noUncheckedIndexedAccess | arr[i]와 record[key]의 타입에 undefined를 포함 |
exactOptionalPropertyTypes | debug?: boolean은 키가 없는 것은 받지만 debug: undefined는 받지 않음 |
noImplicitReturns | 값을 반환하는 함수의 모든 코드 경로가 반환해야 함(TS7030) |
noImplicitOverride | 기반 클래스 메서드를 재정의하는 메서드에 override를 써야 함(TS4114) |
noFallthroughCasesInSwitch | 비어 있지 않은 case는 break, return, throw로 끝나야 함(TS7029) |
noUnusedLocals, noUnusedParameters | 쓰이지 않는 변수와 매개변수를 오류로 처리 |
noPropertyAccessFromIndexSignature | 인덱스 시그니처의 키는 obj.key가 아니라 obj["key"]로 접근해야 함 |
실제 버그를 가장 많이 잡는 것은 noUncheckedIndexedAccess입니다. 켜면 다음과 같습니다.
const scores = [90, 85];
const d: Record<string, number> = {};
const third: number = scores[2]; // error TS2322: Type 'number | undefined' is not assignable to type 'number'.
const c: number = d["x"]; // same error
const safe = scores[2] ?? 0; // number
for (const s of scores) { // for...of is not affected: s is number
console.log(s);
}
TypeScript 7의 tsc --init은 생성하는 설정에서 strict와 함께 noUncheckedIndexedAccess와 exactOptionalPropertyTypes를 켭니다.
자주 묻는 질문
TypeScript에서 strict 모드는 무엇을 하나요?
"strict": true는 더 엄격한 검사 묶음을 켭니다: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, strictBuiltinIteratorReturn. 이들은 함께 any가 조용히 끼어드는 것을 막고, null과 undefined를 타입 시스템의 일부로 만듭니다.
TypeScript에서 strict 모드는 기본으로 켜져 있나요?
TypeScript 7에서는 그렇습니다. strict의 기본값이 true이므로 strict 설정이 없는 프로젝트도 모든 strict 검사를 받습니다. tsc --init도 "strict": true를 명시적으로 씁니다. 끄려면 "strict": false를 써야 합니다.
strict 검사 하나만 끄고 나머지는 유지할 수 있나요?
예. 개별 옵션이 strict보다 우선합니다. { "strict": true, "strictNullChecks": false }는 null 검사를 제외한 모든 strict 검사를 유지합니다. 큰 코드베이스를 옵션 하나씩 마이그레이션할 때 흔히 쓰는 방법입니다.
TypeScript의 strict 모드는 JavaScript의 "use strict"와 같은가요?
아니요. "use strict"는 코드의 동작을 바꾸는 JavaScript 런타임 모드입니다. TypeScript의 strict는 타입 검사기가 보고하는 내용만 바꿉니다. TypeScript 7은 모듈이 아닌 출력에 항상 "use strict"를 쓰며, alwaysStrict: false는 이제 제거된 옵션입니다.
strict에 noUncheckedIndexedAccess가 포함되나요?
아니요. noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitReturns, noImplicitOverride, noFallthroughCasesInSwitch는 직접 켜야 하는 별도 옵션입니다. tsc --init은 생성하는 설정에서 앞의 두 가지를 켭니다.