본문으로 건너뛰기
Advertisement

9.1 tsconfig.json 심화 — strict 모드와 프로젝트 참조

strict 모드의 9가지 세부 옵션

"strict": true는 사실 9가지 컴파일러 옵션의 묶음입니다. 각각을 이해하면 세밀한 조정이 가능합니다.

{
"compilerOptions": {
"strict": true
// 아래 9가지를 모두 true로 설정한 것과 같음
}
}

1. strictNullChecks

nullundefined를 별도 타입으로 취급합니다. 가장 중요한 옵션입니다.

// 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"]
}

고수 팁

옵션별 권장 설정표

옵션신규 프로젝트레거시 마이그레이션
stricttrue점진적으로 활성화
noUncheckedIndexedAccesstrue나중에 추가
noUnusedLocalstrue선택사항
exactOptionalPropertyTypestrue신중하게
skipLibChecktruetrue
Advertisement