본문 바로가기

프론트엔드

타입 좁히기 (타입가드, 커스텀 타입가드)

 

타입 좁히기 (타입가드)

function numberOrStr(a: number | string) {
	if (typeof a === 'string') {
		a.split(',')
	} else { // 여기 타입이 string인 것 정도는 자동으로 파악해 줌
		a.toFixed(1)
	}
}

function numberOrStr(a: number | string) {
	(a as number).toFixed(1); // 이렇게 형변환하면 타입에러가 안난다.
	// 하지만 타입스크립트만 안심시켰을 뿐, 자바스크립트에서는 사라지기 때문에 에러가 발생할 수 있다.
}

⇒ unknown이나 남이 만들어 놓은 타입이 틀렸을 경우를 제외하고는 as는 사용하지 말 것을 추천

⇒ 타입가드를 사용해야 함

클래스

  • 클래스는 타입이 될 수 있지만, A라는 클래스 자체를 의미하는게 아니라 생성자로 인해 생성된 인스턴스를 의미함
  • 클래스 자체의 타입은 typeof A (typeof 클래스) 가 됨
class A {
	aaa() {}
}
class B {
	bbb() {}
}

function aOrB(param: A | B) {
	if(param instanceof A) {
		param.aaa();
	} else {
		...
	}
}

aOrB(A) // error
aOrB(new B())

객체

같은 속성인데 값이 서로 다르거나, 속성 이름이 다른 이 2가지로 객체를 구분할 수 있다.

값으로 구분

type B = { type: 'b', bb: string }
type C = { type: 'c', cc: string }
type D = { type: 'c', dd: string }

function typeCheck (a: B | C | D) {
	if (a.type === 'b') {
		a.bb; // 에러안남
	} else if (a.type === 'c') {
		a.cc // ts가 C타입인지 D타입인지 구분 못하고, D타입엔 cc가 없으므로 에러남
	} else {
		a.dd // a.type은 b와 c 밖에 없으므로 never타입이 되고, 에러가 난다.
	}
}

속성으로 구분

type B = { type: 'b', bb: string }
type C = { type: 'c', cc: string }
type D = { type: 'd', dd: string }

function typeCheck (a: B | C | D) {
	if ('bb' in a) {
		a.type
	} else if ('cc' in a) {
		a.cc
	} else {
		a.dd
	}
}

커스텀 타입 가드

is가 들어간건 커스텀 타입가드 함수이다.

커스텀 타입가드는 if문 안에 사용해서 ts에게 정확한 타입을 알려주는 역할이고, 타입을 판별하는 로직은 직접 쓴다.

간단한건 기본 자바스크립트코드로 쓰지만, 복잡할 경우 만들어서 사용 (is가 아니면 타입 추론이 안 되는 경우도 있다)

interface Cat { meow: number }
interface Dog ( bow: number }

function catOrDog (a: Cat | Dog ): a is Dog {
	if((a as Cat).meow) { return false }
	return true
}

const cat:Cat | Dog = { meow: 3 } 
if(catOrDog(cat)) {
	console.log(cat.meow); 
}

if ('meow' in cat) {
	console.log(cat.meow);
}

실전 예제

const isRejected = (input: PromiseSettledResult<unknown>): input is PromiseRejectedResult => {
	return input.status === 'rejected'
}
const isFulfilled = <T>(input: PromiseSettledResult<T>): input is PromiseFulfilledResult<T> => {
	return input.status === 'fulfilled'
}

const promises = await Promise.allSettled(Promise.resolve('a'), Promise.resolve('b')]);
const errors = promises.filter(isRejected); // error만 받고 싶을 경우

const errors = promises.filter(promise => promise.status === 'rejected');
// -> 같은 코드를 커스텀 타입가드로 안 빼고 이렇게 쓰면ts는 errors를 PromiseSettledResult<string>[]로 넓게 추론한다. 
// -> 이럴 때 커스텀 타입가드를 사용