본문으로 건너뛰기

제어 흐름 분석

CFA(Control Flow Analysis, 제어 흐름 분석)는 대부분 합집합을 사용하고 코드의 논리에 따라 합집합 내부의 타입 수를 줄입니다.

대부분의 경우 CFA는 자연스러운 자바스크립트 불 논리 내에서 작동합니다. 하지만 타입스크립트가 타입을 좁히는 방식에 영향을 주는 고유한 함수를 정의하는 방법도 있습니다.

if

대부분의 좁히기는 if문 내부의 표현식을 사용합니다. 표현식에서 여러 타입 연산자는 새 범위 내에서 좁힙니다.

원시값을 위한 typeof

ts
const input = getUserInput();
const input: string | number
 
if (typeof input === "string") {
console.log(input);
const input: string
}
ts
const input = getUserInput();
const input: string | number
 
if (typeof input === "string") {
console.log(input);
const input: string
}

in을 이용한 객체의 프로퍼티

ts
const input = getUserInput();
const input: Name | Address
 
if ("firstName" in input) {
console.log(input);
const input: Name
}
ts
const input = getUserInput();
const input: Name | Address
 
if ("firstName" in input) {
console.log(input);
const input: Name
}

클래스를 위한 instanceof

ts
const input = getUserInput();
const input: number | number[]
 
if (input instanceof Array) {
console.log(input);
const input: number[]
}
ts
const input = getUserInput();
const input: number | number[]
 
if (input instanceof Array) {
console.log(input);
const input: number[]
}

모든 타입을 위한 타입 가드 함수

ts
const input = getUserInput();
const input: number | number[]
 
if (Array.isArray(input)) {
console.log(input);
const input: number[]
}
ts
const input = getUserInput();
const input: number | number[]
 
if (Array.isArray(input)) {
console.log(input);
const input: number[]
}

표현식

불 연산을 수행할 때 동일한 코드 줄에서도 좁히기가 발생합니다.

ts
const input = getUserInput();
const input: string | number[]
 
const inputLength =
(typeof input === "string" && input.length) || input;
const input: string
ts
const input = getUserInput();
const input: string | number[]
 
const inputLength =
(typeof input === "string" && input.length) || input;
const input: string

구별되는 합집합

ts
type Responses =
| { status: 200, data: any }
| { status: 301, to: string }
| { status: 400, error: Error }
ts
type Responses =
| { status: 200, data: any }
| { status: 301, to: string }
| { status: 400, error: Error }

합집합의 모든 요소는 status라는 동일한 프로퍼티 이름을 가지고 있으며 CFA는 이를 구별할 수 있습니다.

사용법:

ts
const res = getResponse();
const res: Responses
 
switch (res.status) {
case 200:
res.data;
const res: { status: 200; data: any; }
break;
case 301:
redirect(res.to);
const res: { status: 301; to: string; }
break;
case 400:
res.error;
const res: { status: 400; error: Error; }
break;
}
ts
const res = getResponse();
const res: Responses
 
switch (res.status) {
case 200:
res.data;
const res: { status: 200; data: any; }
break;
case 301:
redirect(res.to);
const res: { status: 301; to: string; }
break;
case 400:
res.error;
const res: { status: 400; error: Error; }
break;
}

타입 가드

true이면 새 범위에 대한 CFA 변경을 설명하는 반환 타입을 가진 함수입니다.

ts
function isErrorResponse(obj: Responses): obj is ErrorResponse {
return "error" in obj ? true : false;
}
ts
function isErrorResponse(obj: Responses): obj is ErrorResponse {
return "error" in obj ? true : false;
}

반환 타입 위치에 있는 obj is ErrorResponse는 단언을 묘사합니다.

사용법:

ts
const res = getResponse();
const res: Responses
 
if (isErrorResponse(res)) {
console.log(res);
const res: ErrorResponse
}
ts
const res = getResponse();
const res: Responses
 
if (isErrorResponse(res)) {
console.log(res);
const res: ErrorResponse
}

단언 함수

CFA를 묘사하는 함수는 false를 반환하는 대신 오류를 던지기 때문에 현재 범위에 영향을 줍니다.

ts
function assertResponse(obj: any): asserts obj is SuccessResponse {
if (!(obj instanceof SuccessResponse)) {
throw new Error("Not a success!");
}
}
ts
function assertResponse(obj: any): asserts obj is SuccessResponse {
if (!(obj instanceof SuccessResponse)) {
throw new Error("Not a success!");
}
}

사용법:

ts
// res: SuccessResponse | ErrorResponse
const res = getResponse();
assertResponse(res);
// res: SuccessResponse
ts
// res: SuccessResponse | ErrorResponse
const res = getResponse();
assertResponse(res);
// res: SuccessResponse

단언 함수는 현재 범위를 변경하거나 오류를 던집니다.

할당

as const로 타입 좁히기

객체의 하위 필드는 변경 가능한 것으로 간주됩니다. 할당 중에는 하위 필드의 타입이 리터럴이 아닌 버전으로 확장됩니다. 접두사 as const는 모든 타입을 리터럴 버전으로 잠급니다.

ts
const obj = {
name: "Zagreus"
};
 
console.log(obj);
const obj: { name: string; }
ts
const obj = {
name: "Zagreus"
};
 
console.log(obj);
const obj: { name: string; }
ts
const obj = {
name: "Zagreus"
} as const;
 
console.log(obj);
const obj: { readonly name: "Zagreus"; }
ts
const obj = {
name: "Zagreus"
} as const;
 
console.log(obj);
const obj: { readonly name: "Zagreus"; }

관련 변수 추적하기

ts
const res = getResponse();
const isSuccessResponse
= res instanceof SuccessResponse;
if (isSuccessResponse) {
// res: SuccessResponse
res.data;
}
ts
const res = getResponse();
const isSuccessResponse
= res instanceof SuccessResponse;
if (isSuccessResponse) {
// res: SuccessResponse
res.data;
}

재할당에 의한 타입 갱신

ts
let data = getData();
let data: string | number
 
data = "Hello";
console.log(data);
let data: string
ts
let data = getData();
let data: string | number
 
data = "Hello";
console.log(data);
let data: string