타입스크립트 정리11: type narrowing

Kimhojin_Zeno·2023년 5월 22일
0

타입스크립트 정리

목록 보기
11/13

타입 좁히기란 주로 명확하지 않은 타입이 있을 때 사용하는 것으로

그런 타입을 유니온(union) 타입이라고 하며 이를 보다 명확하게 좁히는 것이다.

값의 타입에 따라 다르게 작동하는 함수

‘ade’ → ‘adeadeade’

3 → 9

이렇게 나오려면 어떻게 해야할까?

Typeof Guards

가장 쉬운 방법은 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 Guards

truthiness 가드를 사용해서 null, undefined, falsy 값을 좁히거나 제거할 수 있다.

const el = document.getElementById("idk");

if(el) {
	console.log(el);
}

if문을 이용해서 null이나 undefined를 피한다.

Equality Narrowing

만약 두 파라미터를 받는데 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 Operator 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 Narrowing

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 타입명제

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 이라는 구문을 뜻함.

재사용이 가능한 함수가 있다는게 중요하다.

discriminated unions 판별 유니온

여러 타입에다가 공통된 프로퍼티를 추가한다. 그 다음 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"
	}
}

Exhaustiveness check 소진검사, Never

가능한 모든 옵션을 다 썼는지 확인하기 위해 사용된다.

위에서 만든 동물 함수를 예시로 든다면

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”)을 추가해서 수정하면 에러가 사라짐.

profile
Developer

0개의 댓글