타입 좁히기란 주로 명확하지 않은 타입이 있을 때 사용하는 것으로
그런 타입을 유니온(union) 타입이라고 하며 이를 보다 명확하게 좁히는 것이다.
값의 타입에 따라 다르게 작동하는 함수
‘ade’ → ‘adeadeade’
3 → 9
이렇게 나오려면 어떻게 해야할까?
가장 쉬운 방법은 typeof라는 가드를 사용하는 것이다.
const isTeenager = (age: number | string) => {
if(typeof age === "string") {
//age가 문자열이면
console.log(age.charAt(0) === 1);
}
if(typeof age === "number") {
//age가 숫자면
console.log(age > 12 && age < 20);
}
}
isTeenager("20") //false
isTeenager(13) // true
typeof를 사용하면 ‘number’ ‘string’ 처럼 타입이 나온다. 그걸로 if로 나눔.
truthiness 가드를 사용해서 null, undefined, falsy 값을 좁히거나 제거할 수 있다.
const el = document.getElementById("idk");
if(el) {
console.log(el);
}
if문을 이용해서 null이나 undefined를 피한다.
만약 두 파라미터를 받는데 x는 string, boolean 이고 y는 string, number라면
x === y 인 경우는 둘 다 string인 경우밖에 없다.
const someFunc = (x: string | boolean, y: string | number) => {
if(x === y) {
x.toUpperCase();
y.toLowerCase();
}
}
둘다 문자열인 경우에만 작동하도록 다른 타입일때는 작동하지 않도록 Narrowing을 할 수 있다.
in 연산자를 사용해서 특정 프로퍼티가 객체에 들어있는지 확인 할 수 있다.
const pet = {name: "kitty", age: 20}
"name" in pet // true
"breed" in pet // false
in연산자를 사용해서 타입을 좁힐 수 있다.
대부분 인터페이스나 타입 별칭을 사용해 객체로 작업을 할때 사용한다.
typeof를 사용해서는 타입이 전부 객체로 나오니 좁히기 불가능.
interface Movie {
title: string,
duration: number
}
interface TVShow {
title: string,
numEpisodes: number,
episodeDuration: number
}
function getRuntime(media: Movie | TVShow) {
if("numEpisodes" in media) { // 해당 프로퍼티가 있다면 TVShow이다.
return media.numEpisodes * media.episodeDuration
}
return media.duration // TVShow인 경우 위에서 처리되었으므로 남는 경우의 수는 Movie.
}
instanceof 연산자는 주어진 값의 프로토타입 속성이 객체의 프로토타입 체인에 존재하는가 판별한다.
function printFullDate(date: string | Date) { // 문자열이나 Date로 받으면
if(data instanceof Date) { // Date 타입인 경우
console.log(date.toUTCString());
} else { // 문자열인 경우
console.log(new Date(date).toUTCString());
}
}
커스텀 클래스에서도 사용할수 있다.
class User {
constructor(public username: string) {}
}
class Company {
constructor(public name: string) {}
}
function printName(entity: User | Company) {
if(entity instanceof User) {
entity
} else {
entity
}
}
받는 파라미터가 User 또는 Company라는 클래스일때
typeof로는 가릴 수 없다. instanceof를 사용하면 가릴 수 있다.
new 키워드를 사용해 초기화한 클래스 등으로 작업을 할때도 사용한다.
쉽게 말해 User클래스에서 인스턴스화되었는지, Date에서 인스턴스화되었는지를 확인하는것.
type predicates의 근본적인 기능은 함수를 정의하는 것.
interface Cat {
name: string;
numLives: number;
}
interface Dog {
name: string;
breed: string;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).numLives !== undefined;
}
// 전달된 animal에 numLives 프로퍼티가 있는지 아닌지 여부에 따라 true or false.
// as는 타입 단언.
function makeNoise(animal: Cat | Dog): string {
if(isCat(animal)) {
return "MEOW"
}
}
재사용이 가능한 함수를 만들어 타입이 무엇인지 알려주는 기능을 하도록 한다.
주로 isXX라는 이름으로 함수를 만든다.
animal is Cat의 역할은 ‘이 함수가 true를 반환하면, 여기에 전달된 값이 Cat이다’라는 뜻이다.
즉 true이면 → animal is Cat.
isCat과 makeNoise는 파라미터 이름이 일치해야 한다. animal.
타입명제는 animal is Cat 이라는 구문을 뜻함.
재사용이 가능한 함수가 있다는게 중요하다.
여러 타입에다가 공통된 프로퍼티를 추가한다. 그 다음 switch등 조건문으로 비교해서 타입을 결정한다.
interface Rooster {
name: string;
weight: number;
age: number;
kind: "rooster"
}
interface Cow {
name: string;
weight: number;
age: number;
king: "cow";
}
interface Pig {
name: string;
weight: number;
age: number;
kind: "pig"
}
type FarmAnimal = Pig | Rooster | Cow;
function getFarmAnimalSound(animal: FarmAnimal) {
switch(animal.kind) {
case("rooster"):
return "koko";
case("pig"):
return "Oink";
case("cow"):
return "Moo"
}
}
가능한 모든 옵션을 다 썼는지 확인하기 위해 사용된다.
위에서 만든 동물 함수를 예시로 든다면
function getFarmAnimalSound(animal: FarmAnimal) {
switch(animal.kind) {
case("rooster"):
return "koko";
case("pig"):
return "Oink";
case("cow"):
return "Moo"
default: // 절대로 여기까지 내려오지 않게 할 수 있다.
const shouldNeverGetHere: never = animal
}
}
예를들어 로그인이나 인증 같은 문제에서 다양한 오류 상태를 처리해야한다
빠뜨린게 없는지 체크.
never타입은 어디든 할당되지만 never에는 어떤 타입도 할당할 수 없다.
따라서 만약 이 단계까지 내려온다면 오류가 발생하게 됨. animal은 never에 할당되지 않기 때문.
새로운 동물 종류를 추가한다고 하자.
interface Rooster {
name: string;
weight: number;
age: number;
kind: "rooster"
}
interface Cow {
name: string;
weight: number;
age: number;
king: "cow";
}
interface Pig {
name: string;
weight: number;
age: number;
kind: "pig"
}
interface Sheep {
name: string;
weight: number;
age: number;
kind: "sheep"
}
type FarmAnimal = Pig | Rooster | Cow | Sheep
function getFarmAnimalSound(animal: FarmAnimal) {
switch(animal.kind) {
case("rooster"):
return "koko";
case("pig"):
return "Oink";
case("cow"):
return "Moo"
default: // 절대로 여기까지 내려오지 않게 할 수 있다.
const shouldNeverGetHere: never = animal
}
}
Sheep이 추가되었는데 밑에 함수에서는 Sheep을 처리하지 않았다.
이때 shouldNeverGetHere에 에러가 뜬다.
sheep을 처리하지 않았는데, ‘sheep은 never가 될수없다’는 내용.
즉 sheep을 처리하지 않았기 때문에 밑에까지 도달한 것이다.
case(”sheep”)을 추가해서 수정하면 에러가 사라짐.