2021. 5. 30. 15:15ㆍ프론트엔드/Typescript
내로윙(Narrowing)이란?
Narrowing은 실제로 선언된 타입들에 대하여 더 구체적인 타입에 대해 처리하는 것.
예를 들어 'name: string | number'라고 한다면,
string일 때와 number일 때를 구분하여 처리하는 과정을 말한다.
Narrowing에 의의는 각 타입에 대한 처리를 분명하게 하여,
에러가 나지 않는 안전한 코드를 만드는 것이라 할 수 있다.
typeof를 이용한 타입 가드
원시 타입 (primitives)을 체크하기 위해서는 typeof를 사용합니다
function printAll(strs: string | string[] | null) {
if (typeof strs === "string") {
console.log(strs);
} else {
// do nothing
}
}
typeof로 구분 가능한 type은 다음과 같습니다
string / number / bigint / boolean / symbol / undefined / object / function
Truthiness Narrowing
글에서도 Truthiness는 일상에서 사용하는 단어는 아니라고 한다.
&&, ||, if, ! 등을 이용하여 구분하는 것을 말한다.
if문에서는 어떤 타입이건 boolean타입이 되도록 coerce(강제)한다.
그래서 다음과 같이 처리를 할 수 있다.
function getUsersOnlineMessage(numUsersOnline: number) {
if (numUsersOnline) {
return `There are ${numUsersOnline} online now!`;
}
return "Nobody's here. :(";
}
numUsersOnline만 if문 안에 있지만, 0이면 false, 그 이외의 값에 대해서는 true가 된다.
다른 예로는 이런 게 있다.
function printAll(strs: string | string[] | null) {
if (strs && typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
}
}
이렇게 되면 에러가 뜬다.
왜냐하면 if( typeof strs === "object")는
string[]뿐만 아니라 null도 통과시킨다.
등호를 이용한 내로윙 (Equality Narrowing)
자바스크립트에는 ==말고도 ===(자매품 !==)이 있다.
이 차이는 다음과 같다.
interface Container {
value: number | null | undefined;
}
function multiplyValue(container: Container, factor: number) {
// Null과 undefined가 동시에 없어짐
if (container.value != null) {
console.log(container.value);
}
// null만 없어짐
if (constainer.value !== null) {
//undefined일 수도 있음
console.log(container.value)
}
}
null, undefined외에도 사용은 다양하다.
const a: number = 5;
const b: string = "5";
function check(a: string | number, b: string | boolean) {
if (a == b) {
console.log("a");
}
if (a === b) {
console.log("b")
}
}
결과는 a만 출력이 된다. javascript에서는 5 == "5"도 true로 처리가 된다.
하지만 ===을 사용하면 타입과 값 둘다 같아야 true로 처리가 된다.
in 연산자를 이용한 내로윙 (in operator narrowing)
오브젝트를 체크할 때, 해당 오브젝트 안에 특정 키가 있는지 확인할 수 있다.
type Marine = { steampack: () => void };
type FireBat = { steampack: () => void };
type Ghost = { lockdown: () => void };
function useSteampack(unit: Marine | FireBat | Ghost) {
if ("steampack" in unit) {
return unit.steampack();
} else {
// do nothing
}
}
스타크래프트에서 마린과 파이어뱃, 고스트를 한 부대로 지정하고서 스팀팩을 사용하는 경우를 생각해보자.
고스트는 스팀팩이 없기 때문에, unit.steampack()을 사용하면 에러가 날 것이다.
다음과 같이 steampack이 있는 경우에만 실행되도록 하면 된다.
instanceof 연산자를 이용한 내로윙 (instanceof Narrowing)
원시 타입(primitives)은 typeof로 처리했다.
그렇다면 객체는 어떻게 처리할까?
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
} else {
console.log(x.toUpperCase());
}
}
다음과 같이 instanceof 연산자를 이용해서 해당 클래스의 객체인지 확인한다.
이는 기존 Javascript의 Foo.protoType과도 같다고 한다.
대입을 통한 내로윙 (Assignments)
예제가 재미있어서 가져와봤다.
let x = Math.random() < 0.5 ? 10 : "hello world!";
뭐 이런 슈뢰딩거 같은 코드가 다 있지 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
아무튼 이런 상태라면 type은 'string | number'가 된다.
그리고 여기에 한 종류로 대입을 하면 해당 타입으로 인식을 한다.
// x: number
x = 1;
// x: string
x = "goodbye!";
is를 이용하는 방법 (type predicates)
어떤 변수가 어떤 타입인지 알고 싶다면 다음과 같이 해보자.
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
이 함수의 최종 목적은 반환 타입인 'pet is Fish'라고 할 수 있다.
이걸 어디다 쓰냐고 할 수 있는데, iteration을 하는 상황이나 if를 이용한 분기에 사용할 수 있다ㅏ.
// if 분기에 사용하기
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
// iterator에 사용하기
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === "sharkey") return false;
return isFish(pet);
});
never을 이용한 잔여 케이스 체크 (exhaustiveness checking with never)
우리가 if else나 switch를 사용하다보면 모든 case를 소화했나를 확인하고 싶을 때가 있다.
그럴때는 never type을 이용해보자.
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
never type에는 어떠한 값도 들어와선 안된다.
정확히는 어떠한 값이 들어올만한 상황이면 안 된다.
한마디로 '대입되면 터지는 지뢰'에 가깝다.
https://www.typescriptlang.org/docs/handbook/2/narrowing.html
'프론트엔드 > Typescript' 카테고리의 다른 글
TypeScript - HandBook(Object Types) (0) | 2021.06.08 |
---|---|
TypeScript - Handbook(More on Functions) (0) | 2021.06.01 |
TypeScript - Handbook(Everyday Types) (0) | 2021.05.28 |
TypeScript - Handbook(The Basics) (0) | 2021.05.28 |
TypeScript - Handbook(TypeScript for JS Programmers) (0) | 2021.05.26 |