Type Narrowing 이란?
- Narrowing 이란 영어 자체의 뜻으로 봤을 때 범위를 좁힌다는 뜻이다.
- 처음 선언한 것보다 더 구체적인 유형으로 구체화하는 프로세스를 Narrowing 이라고 한다.
- 타입스크립트에서 어떻게 타입 유추를 정확하게 가져가는지에 대한 논리를 말한다.
- 주요 기술로는 Type Guards (IF 로 검사), Type Assertions (수동 재정의), Control Flow Analysis (프로그램 흐름 분석) 가 있다.
- Narrowing 을 통해 Union 타입에서 더욱 구체적인 타입으로 논리적으로 유추가 가능하다.
Narrowing 의 간단한 코드 예제
- 아래의 예제는 Type Guards 를 이용한 예제이다.
//Narroiwng 예제
function processInput(input: string | number) {
// Narrowing using typeof
if (typeof input === "string") {
console.log("Input is a string:", input);
// 여기서, 타입스크립트는 input 이 string 이란 것을 알 수 있다.
} else {
console.log("Input is a number:", input);
// 여기서, 타입스크립트는 input 이 number 란 것을 알 수 있다.
}
}
// Example usages
processInput("Hello");
processInput(123);
Narrowing 의 종류
- 아래의 종류는 타입스크립트 핸드북 에도 잘 나와있다.
- 코드팩토리님 인프런 강의 에서도 잘 알려준다. 난 이걸 보면서 공부했다.
- Assignment Narrowing
- Typeof Narrowing
- Truthiness Narrowing
- Equality Narrowing
- In operator Narrowing
- Instanceof Narrowing
- Discriminated union Narrowing (차별된 유니언 내로잉)
- Exhaustiveness checking
Assignment Narrowing
- 값을 할당하는 순간 Narrowing 이 적용되어 타입 유추가 가능해지는 것을 말한다.
// 선언할 때는 number | string 타입으로 타입이 모호함
let nOs: number | string = "abc"; // 값이 할당된 이후에 Narrowing 이 진행되어 string 타입이 됨
nOs.toUpperCase(); // string 타입에만 존재하는 toUpperCase() 메서드 이용 가능
참고: random()
함수
- 아래 예제부터 쭉
random()
이라는 함수가 등장하는데,random()
함수가 등장하는 이유는 런타임까지 타입이 무엇인지 모르는 상태에서 어떻게 Narrowing 이 진행되는지를 보여주기 위해서이다. random()
함수의 정의는 아래와 같다.
/**
* 50% 확률로 true or false 반환
* @returns {boolean} true or false
*/
function random() {
return Math.random() > 0.5;
}
Typeof Narrowing
- 자바스크립트에서 타입을 확인할 때 사용하는
typeof
를 통해 직접적으로 Narrowing 하는 방식이다.
nOs = random() ? "a" : 1;
if (typeof nOs === "string") {
nOs.toUpperCase(); // 여기서는 당연히 string 타입 취급
} else {
nOs.toFixed(2); // 여기서는 string 이 아니기 때문에 number 취급을 받을 수 밖에 없음
}
Truthiness Narrowing
if()
조건문에서true
로 걸린다면, Narrowing 되는 방식이다.
let nullOrString: null | string[] = random() ? null : ["a", "b"];
if (nullOrString) {
nullOrString; // string[] 취급을 받음 (null 은 if() 를 통과하지 못함)
} else {
nullOrString; // null 취급을 받음 (if() 를 통과하지 못했으면 null 임)
}
Equality Narrowing
===
(Equality) 키워드를 통해 Narrowing 되는 방식이다.
let numOrString: number | string = random() ? 1 : "x";
let numOrBool: number | boolean = random() ? 1 : true;
if (numOrString === numOrBool) {
// 둘이 타입이 같으므로 여기서는 둘 다 number 취급
numOrString;
numOrBool;
} else {
// 여기서는 둘이 타입이 같지는 않은 것을 확인했으므로 둘 다 number, string 혹은 number, boolean 이 될 수 있음
numOrString;
numOrBool;
}
let numOrStringOrUndefined: number | string | undefined = random()
? 1
: random()
? "x"
: undefined;
// 사실상 typeof narrowing 과도 같음
if (typeof numOrStringOrUndefined === "number") {
numOrStringOrUndefined; // number 로 추론됨
} else {
numOrStringOrUndefined; // number 를 제외한 string 혹은 undefined 로 추론됨
}
Inoperator Narrowing
- 자바스크립트에서 해당 프로퍼티가 존재하는지 확인할 수 있는
in
키워드를 통해 Narrowing 하는 방식이다.
interface Person {
name: string;
age: number;
}
interface Company {
location: string;
name: string;
}
let person: Person = {
name: "JAKE",
age: 20,
};
let company: Company = {
location: "SEOUL",
name: "my crab soft",
};
let personOrCompany: Person | Company = random() ? person : company;
// 자바스크립트 `in` 키워드를 통해 `location` 이라는 프로퍼티가 존재하는지 확인
if ("location" in personOrCompany) {
personOrCompany; // location 이라는 프로퍼티 키가 존재한다면, 무조건 Company
}
if ("name" in personOrCompany) {
personOrCompany; // name 이라는 프로퍼티 키가 존재한다면, Person 혹은 Company 둘 다 가능
}
Instanceof Narrowing
- 자바스크립트에서 어떤 클래스를 인스턴스화 시켰는지 확인할 수 있는
instanceof
를 통해 Narrowing 하는 방식이다.
let dateOrString: Date | string = random() ? new Date() : "abc";
if (dateOrString instanceof Date) {
dateOrString; // Date 타입 취급
} else {
dateOrString; // string 타입 취급
}
Discriminated Union Narrowing
- 예제를 보면, 옵셔널(
?
)을 통해 모호하게 타입을 나누던 것을 옵셔널을 제거하고 더 명확하게 타입을 나눈것으로 볼 수 있다. - 타입스크립트 타입 설계 시에 아래와 같은 옵셔널 사용은 피하는 것이 모범 사례로 볼 수 있다.
interface SmartPhone {
type: "galaxy" | "iphone";
appleLogo?: true;
samsungLogo?: true;
}
let phone: SmartPhone = random()
? {
type: "galaxy",
samsungLogo: true,
}
: {
type: "iphone",
appleLogo: true,
};
if (phone.type === "galaxy") {
phone.samsungLogo; // 추론 불가 (true | undefined)
} else {
phone.appleLogo; // 추론 불가 (true | undefined)
}
interface Galaxy {
type: "galaxy";
samsungLogo: true;
}
interface Iphone {
type: "iphone";
appleLogo: true;
}
type GalaxyOrIphone = Galaxy | Iphone;
// 이전에 phone 변수에 할당했던 것과 동일하게 할당
let galaxyOrIphone: GalaxyOrIphone = random()
? {
type: "galaxy",
samsungLogo: true,
}
: {
type: "iphone",
appleLogo: true,
};
if (galaxyOrIphone.type === "galaxy") {
galaxyOrIphone; // Galaxy 로 정상적으로 추론됨
} else {
galaxyOrIphone; // Iphone 으로 정상적으로 추론됨
}
Exhaustiveness Checking
switch ... case
문에서 유용하게 사용할 수 있다.case
에 모든type
을 검증했는지 아래의never
타입 체크에서 확실히 검증이 된다.
switch (galaxyOrIphone.type) {
case "galaxy":
galaxyOrIphone; // Galaxy 타입
break;
case "iphone":
galaxyOrIphone; // Iphone 타입
break;
default:
galaxyOrIphone; // never 타입
// 나중에 Galaxy, Iphone 이후에 Xiaomi 타입도 올 수 있는데
// 이 때 확실히 모든 타입을 case 에서 처리했는지 검증 가능
// 타입스크립트를 스마트하게 활용할 수 있는 방법 중 하나임
const _check: never = galaxyOrIphone;
break;
}
반응형
'Typescript' 카테고리의 다른 글
TS011. 타입스크립트로 함수 시그니처 선언하기 (0) | 2023.12.28 |
---|---|
TS010. 타입스크립트 함수 정의 (Function Definition) (0) | 2023.12.28 |
TS008. Typescript Intersection 이란? (0) | 2023.12.28 |
TS007. 타입스크립트 Union 이란? (0) | 2023.12.28 |
TS006. 타입스크립트 타입 캐스팅 및 주의점 (0) | 2023.12.27 |