
아래 예제에서 pet 에 어느 타입이 들어오든 공통으로 가지고 있는 메소드에는 접근이 가능하다. 하지만 어느 한 쪽이 가지고 있지 않은 메소드 실행이 필요한 경우에는 어떻게 해야할까?
interface Bird {
fly: () => void;
layEggs: () => void;
}
interface Fish {
swim: () => void;
layEggs: () => void;
}
function getSmallPet(): Fish | Bird {
return {
fly: function() {},
layEggs: function() {}
};
}
const pet = getSmallPet();
pet.layEggs(); // 공통으로 가지고 있는 메소드로 접근이 가능하다.
pet.swim(); // 어느 한쪽이 가지고 있지 않은 메소드로 오류가 발생한다.
타입스크립트에서 pet.swim() 은 컴파일 타임에 오류가 발생한다. pet 의 타입은 Fish | Bird 로 공통으로 가지고 있지 않은 메소드인 .swim 이나 .fly 메소드를 사용할 수 없다.
하지만 타입 단언을 사용해서 이 문제를 해결할 수 있다.
interface Bird {
fly: () => void;
layEggs: () => void;
}
interface Fish {
swim: () => void;
layEggs: () => void;
}
function getSmallPet(): Fish | Bird {
return {
fly: function() {},
layEggs: function() {}
};
}
let pet = getSmallPet();
// 타입 단언
if((pet as Fish).swim) {
(pet as Fish).swim();
} else {
(pet as Bird).fly();
}
위 섹션에서 타입 단언을 사용해서 접근하니 코드의 가독성이 상당히 떨어지고 비효율적인 코드가 되었다. 이러한 상황에서 타입 가드를 적용해서 해결할 수 있다.
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
일반적으로 타입 가드 함수 이름은 isFish와 같이 is를 이름 앞에 붙여서 사용한다.
반환 타입을 작성하는 위치에서 볼 수 있는 is는 타입 가드에서 사용되는 키워드이다. pet is Fish는 파라미터 pet이 Fish 타입인지 구분하는 키워드이다.
return 문을 보면 (target as Developer).swim !== undefined형태로 pet에 swim 프로퍼티가 존재하면 Fish타입으로 취급한다.
interface Bird {
fly: () => void;
layEggs: () => void;
}
interface Fish {
swim: () => void;
layEggs: () => void;
}
function getSmallPet(): Fish | Bird {
return {
fly: function() {},
layEggs: function() {}
};
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
let pet = getSmallPet();
if(isFish(pet)) {
pet.swim(); // Fish
} else {
pet.fly(); // Bird
}
pet 이 if 문 안에서 Fish 라는 것을 알고 있고, else 문 안에서는 Fish 타입이 아니라는 걸 알고 있으므로 Bird 를 반드시 가지고 있다.
in 연산자는 타입을 좁히는 표현으로 작용한다.
n in x 표현에서 n 은 문자열 혹은 리터럴 타입이고 x 는 유니언 타입이다.
function move(pet: Fish | Bird) {
if('swim' in pet) {
return pet.swim();
}
return pet.fly();
}
값이 원시 타입인지 아닌지 확인하는 함수를 정의할 수 있다.
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
하지만 원시 타입을 확인하기 위해서 함수를 모두 작성하는 것은 상당히 번거롭다. 다행히 타입스크립트는 typeof 를 타입 가드로 인식한다.
function valueFunc(value: number | string) {
// typeof 타입 가드로 사용할 수 있다.
if(typeof value === 'string') {
return value.length; // string
}
return value; // number
}
instanceof 타입 가드는 생성자 함수를 사용하여 타입을 좁힐 수 있다.
interface Person {
// name: string;
getName: () => string;
}
class Employee implements Person {
constructor(private name: string) { }
getName() {
return this.name;
}
}
class Employer implements Person {
constructor(private name: string) { }
getName() {
return this.name;
}
}
function getClass(isEmployee: boolean = true) {
return isEmployee ?
new Employee('Employee')
: new Employer('Employer');
}
let person: Person = getClass(true);
if(person instanceof Employee) {
person; // person 타입은 Employee
}
if(person instanceof Employer) {
person; // person 타입은 Employer
}
타입스크립트에서 null 과 undefined 는 모든 타입에서 유효한 값이다. 즉, 방지하고 싶어도 어떤 타입에 할당되는 것을 방지할 수 없다.
위 문제는 --strictNullChecks 플래그로 해결 가능하다. 변수를 선언할 때 자동으로 null 이나 undefined 를 포함하지 않으며 유니온 타입을 사용해서 명시적으로 포함할 수 있다.
// --strictNullChecks 플래그로 null을 포함하지 못하게 한다.
let str = "str";
str = null; // 오류
// 유니온 타입을 사용해서 명시적으로 null을 포함한다.
let str2: string | null = "atr2";
str2 = null;
--strictNullChecks를 적용하면, 선택적 매개변수에 | undefined를 자동으로 추가한다.
// 선택적 매개변수
function fun(x: number, y?: number) {
return x + (y || 0);
}
fun(1, 2);
fun(1);
fun(1, undefined); // | undefined가 자동으로 추가되므로 오류가 발생하지 않는다.
fun(1, null); // 오류, 'null' 은 number | undefined에 할당할 수 없다.
// 선택적 프로퍼티
class C {
a: number;
b?: number;
}
let c = new C();
c.a = 1;
c.b = 2;
c.b = undefined; // | undefined가 자동으로 추가되므로 오류가 발생하지 않는다.
c.b = null; // 오류, 'null' 은 number | undefined에 할당할 수 없다.
실제로 새로운 타입을 만들는 것이 아니라 그 타입을 나타내는 새로운 이름을 만드는 것이다.
type Name = string;
type NameResolver = () => string;
type NameOrResolver = name | NameResolver;
function getName(n: NameOrResolver): Name {
// Name Type
if(typeof n === 'string') { return n; }
// NameResolver Type
else { return n(); }
}
인터페이스와 마찬가지로, 타입 별칭도 제네릭이 될 수 있다.
type Container<T> = { value: T };
프로퍼티 안에서 자기 자신을 참조하는 타입 별칭을 가질 수 있다.
type Tree<T> {
value: T;
left: Tree<T>;
right: Tree<T>;
}
교차 타입과 같이 사용하면, 아주 놀라운 타입을 만들 수 있다.
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;