typescript의 type guard
union type인 객체를 사용할떄
공통으로 가지고 있는 프로퍼티가 아닌 프로퍼티를 접근한 경우 typescript 에서 오류를 발생시킨다.
해당 객체가 실제로 어떤 타입일지 알 수 없기 때문에 발생하는 현상인데(tsc입장에서는 당연한 스펙) 아래에 설명할 type guard
를 사용하여 안전하게 접근이 가능하다.
예제 코드
function getNumberOrString(): number | string {
if (Math.random() < 0.5) {
return 100;
} else {
return "string";
}
}
let numOrStr = getNumberOrString();
numOrStr.toString(); // 문제 없음
numOrStr.toUpperCase(); // 오류, toUpperCase가 없을 가능성이 있음
아래의 내용들은 공식 문서의 설명을 간단히 정리한 내용이다.
type assertion으로 우회
type assertion으로 프로퍼티 접근을 가능하게 만든후 실제로 프로퍼티가 있는지 테스트를 한 후 실제 프로퍼티에 접근한다. . 구문이 복잡하고 영 깔끔해 보이지 않으므로 아래에 소개되는 진짜 type guard
를 쓰는 것이 바람직하다.
if ((numOrStr as string).toUpperCase) {
(numOrStr as string).toUpperCase();
}
type guard
공식 문서의 설명을 옮기자면 특정 스코프에서 타입을 보장하도록 런타임 체크를 수행하는 문법이라고 한다.
A type guard is some expression that performs a runtime check that guarantees the type in some scope.
Using type predicates
parameterName is Type
형태의 type predicate를 리턴 타입으로 하는 함수를 만들고 타입체크 결과를 boolean으로 리턴하도록 한다. 이 함수가 true인 if 분기의 스코프에서는 타입을 보장 받는다.
function isString(target: number | string): target is string /* type predicate */ {
return typeof target == 'string'; // 사실 boolean을 리턴한다
}
if (isString(numOrStr)) {
// 여기 스코프 안에서는 string이라고 보장
numOrStr.toUpperCase();
}
함수를 바로 사용하지 않고 변수에 할당후 사용할 경우 단순 boolean 변수로 type guard
가 아니다
const resultOfIsString = isString(numOrStr);
if (resultOfIsString) {
numOrStr.toUpperCase(); // 오류 발생
}
Using the in operator
in
operator의 true 분기에서는 해당 속성이 있는것을 보장하고 false 분기에서는 없는것을 보장 한다고 문서에 적혀있다.
in
으로 검사한 속성만 쓸수 있을꺼 같지만 타입으로 해석 하여 타입내 다른 속성도 접근 가능하다. (아래 테스트 참고)
다만 primitive에 대해서는 사용이 불가능하고 any, object type, type parameter에 한해서 사용할 수 있다.
interface A {
a();
aa();
}
interface B {
b();
bb();
}
function getAOrB(): A | B {
if (Math.random() < 0.5) {
return {
a: () => {},
aa: () => {},
};
} else {
return {
b: () => {},
bb: () => {},
};
}
}
const aOrB = getAOrB();
if ("a" in aOrB) {
aOrB.a(); // in 체크 성공으로 오류 없음
}
if ("a" in aOrB) {
aOrB.aa(); // a만 체크했지만 타입이 A임을 알기 때문에 오류 없음
}
if ("a" in aOrB) {
aOrB.b(); // 얘는 당연히 안된다
}
let numOrStr = getNumberOrString();
if ('toUpperCase' in numOrStr) { // 여기서 오류, "The right-hand side of an 'in' expression must be of type 'any', an object type or a type parameter."
numOrStr.toUpperCase();
numOrStr.toLowerCase();
}
typeof type guards
typeof
를 아래 두가지 형태로 사용하여 가드로 사용할 수도 있다.
typeof v === "typename"
typeof v !== "typename"
다만 여기서도 제약이 있는데 typeof로 체크가 가능한 number, string, boolean, symbol에만 쓸 수 있다. 인터페이스나 클래스는 typeof로 확인이 안되기 때문에 당연한 스펙으로 봐야겠다.
if (typeof numOrStr === 'string') {
numOrStr.toUpperCase(); // 통과
}
if (typeof numOrStr == 'string') {
numOrStr.toUpperCase(); // 이것도 통과
}
instanceof type guards
typeof
에서 구분할 수 없는 object형에 대한 클래스 가드는 instanceof
로 확인 할 수 있다.
class A {
a() {}
aa() {}
}
class B {
b() {}
bb() {}
}
function getAOrB(): A | B {
if (Math.random() < 0.5) {
return new A();
} else {
return new B();
}
}
const aOrB = getAOrB();
if (aOrB instanceof A) {
aOrB.a(); // 통과
aOrB.aa(); // 통과
}
if (aOrB instanceof A) {
aOrB.b(); // 오류
}