9.1 tsconfig.json 심화 — strict 모드와 프로젝트 참조
strict 모드의 9가지 세부 옵션
"strict": true는 사실 9가지 컴파일러 옵션의 묶음입니다. 각각을 이해하면 세밀한 조정이 가능합니다.
{
"compilerOptions": {
"strict": true
// 아래 9가지를 모두 true로 설정한 것과 같음
}
}
1. strictNullChecks
null과 undefined를 별도 타입으로 취급합니다. 가장 중요한 옵션입니다.
// strictNullChecks: false (위험)
let name: string = null; // ✅ 허용되어버림
name.toUpperCase(); // 런타임 오류 발생!
// strictNullChecks: true (안전)
let name: string = null; // ❌ null은 string에 할당 불가
let safeName: string | null = null; // ✅ 명시적으로 null 허용
if (safeName) {
safeName.toUpperCase(); // ✅ null 체크 후 안전 사용
}
2. strictFunctionTypes
함수 타입의 매개변수가 **반공변적(contravariant)**으로 체크됩니다.
type Handler = (event: MouseEvent) => void;
// strictFunctionTypes: false
const handler: Handler = (event: Event) => { }; // 허용 (불안전)
// strictFunctionTypes: true
const handler: Handler = (event: MouseEvent) => { }; // ✅
const handler2: Handler = (event: Event) => { }; // ❌ MouseEvent가 더 구체적이어야 함
3. strictBindCallApply
bind, call, apply 메서드의 타입을 엄격하게 체크합니다.
function greet(name: string, age: number): string {
return `${name} is ${age}`;
}
// strictBindCallApply: false
greet.call(null, 'Alice', '30'); // ❌ 허용되어버림 ('30'은 string)
// strictBindCallApply: true
greet.call(null, 'Alice', 30); // ✅
greet.call(null, 'Alice', '30'); // ❌ 오류: number 자리에 string
4. strictPropertyInitialization
클래스 프로퍼티가 생성자에서 초기화되는지 확인합니다.
class User {
// strictPropertyInitialization: true
name: string; // ❌ 초기화되지 않음
email: string = ''; // ✅ 기본값 설정
role: string; // ❌
constructor(name: string) {
this.name = name; // ✅ 생성자에서 초기화
// this.role 누락 — 오류
}
}
// 해결 방법들
class UserFixed {
name: string;
email: string = '';
role!: string; // ! : 나중에 반드시 할당됨을 보장 (non-null assertion)
optionalField?: string; // 선택적 프로퍼티
constructor(name: string) {
this.name = name;
}
}
5. noImplicitAny
암묵적 any 타입 사용 시 오류를 발생시킵니다.
// noImplicitAny: false
function process(data) { // data: any (암묵적)
return data.toUpperCase(); // 런타임 오류 가능성 있음
}
// noImplicitAny: true
function process(data: string): string { // ✅ 명시적 타입
return data.toUpperCase();
}
6. noImplicitThis
this의 타입이 암묵적으로 any일 때 오류를 발생시킵니다.
// noImplicitThis: true
function greet(this: { name: string }) {
console.log(`Hello, ${this.name}`); // ✅
}
const obj = { name: 'Alice', greet };
obj.greet(); // ✅
7. alwaysStrict
출력 JavaScript에 "use strict"를 추가합니다.
8. useUnknownInCatchVariables (TS 4.4+)
catch 절의 변수 타입을 any 대신 unknown으로 처리합니다.
try {
await fetchData();
} catch (error) {
// useUnknownInCatchVariables: false: error는 any
// useUnknownInCatchVariables: true: error는 unknown
if (error instanceof Error) {
console.error(error.message); // ✅ 타입 가드 후 안전 사용
}
}
9. exactOptionalPropertyTypes (TS 4.4+)
선택적 프로퍼티에 undefined를 명시적으로 할당하는 것을 방지합니다.
interface Config {
timeout?: number; // 없을 수도 있음 (undefined와 다름)
}
// exactOptionalPropertyTypes: true
const config: Config = { timeout: undefined }; // ❌ undefined 명시 불가
const config2: Config = {}; // ✅ 프로퍼티 없음
const config3: Config = { timeout: 5000 }; // ✅
자주 쓰는 추가 옵션들
{
"compilerOptions": {
// 안전성 강화
"noUncheckedIndexedAccess": true, // 인덱스 접근 시 undefined 포함
"noImplicitOverride": true, // override 키워드 명시 강제
"noPropertyAccessFromIndexSignature": true, // 인덱스 시그니처는 [] 접근만
// 오류 감지
"noUnusedLocals": true, // 사용하지 않는 지역 변수 오류
"noUnusedParameters": true, // 사용하지 않는 매개변수 오류
"noFallthroughCasesInSwitch": true, // switch 문 fallthrough 오류
// 출력 제어
"noEmitOnError": true, // 오류 있으면 JS 출력 안 함
"removeComments": true, // 주석 제거
"sourceMap": true // 소스맵 생성
}
}
noUncheckedIndexedAccess 예시
// noUncheckedIndexedAccess: true
const arr: string[] = ['a', 'b', 'c'];
const first = arr[0]; // 타입: string | undefined (안전!)
if (first) {
console.log(first.toUpperCase()); // ✅
}
const obj: Record<string, number> = { a: 1 };
const val = obj['b']; // 타입: number | undefined
Project References (프로젝트 참조)
대규모 모노레포에서 TypeScript 컴파일 속도를 높이기 위한 기능입니다.
기본 구조
workspace/
├── packages/
│ ├── shared/
│ │ ├── src/
│ │ └── tsconfig.json
│ ├── api/
│ │ ├── src/
│ │ └── tsconfig.json
│ └── web/
│ ├── src/
│ └── tsconfig.json
└── tsconfig.json
shared 패키지 설정
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true, // 프로젝트 참조 활성화에 필수
"declaration": true, // .d.ts 파일 생성 필수
"declarationMap": true, // 소스 탐색을 위한 맵
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"include": ["src"]
}
api 패키지 설정 (shared 참조)
// packages/api/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"references": [
{ "path": "../shared" } // shared 패키지 참조
],
"include": ["src"]
}
루트 tsconfig.json
// tsconfig.json (루트)
{
"files": [], // 루트에서는 파일 컴파일 안 함
"references": [
{ "path": "packages/shared" },
{ "path": "packages/api" },
{ "path": "packages/web" }
]
}
빌드 명령
# 프로젝트 참조 빌드 (변경된 패키지만 재빌드)
tsc --build # 또는 tsc -b
tsc --build --clean # 빌드 결과물 삭제
tsc --build --force # 강제 전체 재빌드
tsc --build --watch # 변경 감지 모드
composite 옵션의 의미
composite: true를 설정하면:
declaration: true가 강제됨incremental: true가 활성화됨rootDir이 tsconfig.json 위치로 강제됨.tsbuildinfo파일이 생성됨
incremental 빌드
{
"compilerOptions": {
"incremental": true, // 증분 빌드 활성화
"tsBuildInfoFile": ".tsbuildinfo" // 빌드 캐시 파일 위치
}
}
변경된 파일만 재컴파일하여 빌드 속도를 크게 향상시킵니다.
실전 tsconfig 구성 패턴
베이스 설정 분리
// tsconfig.base.json
{
"compilerOptions": {
"target": "ES2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
// tsconfig.json (앱 설정)
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"noEmit": true
},
"include": ["src"]
}
// tsconfig.node.json (빌드 도구 설정)
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "node"
},
"include": ["vite.config.ts", "scripts/**/*.ts"]
}
고수 팁
옵션별 권장 설정표
| 옵션 | 신규 프로젝트 | 레거시 마이그레이션 |
|---|---|---|
strict | true | 점진적으로 활성화 |
noUncheckedIndexedAccess | true | 나중에 추가 |
noUnusedLocals | true | 선택사항 |
exactOptionalPropertyTypes | true | 신중하게 |
skipLibCheck | true | true |