본문으로 건너뛰기

제어 흐름 분석

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