타입스크립트는 기본적으로 structural type system을 따르고 있다. structural type system은 구조가 같은 경우 같은 타입으로 본다.
interface IPerson {
name: string;
age: number;
speak(): string;
}
type PersonType = {
name: string;
age: number;
speak(): string;
};
let personInterface: IPerson = {} as any;
let personType: PersonType = {} as any;
personInterface = personType;
personType = personInterface;
구조가 같으면 동일하게 취급하기 때문에 일일이 타입을 만들지 않아도 된다는 장점이 있다.
이와 달리 nominal type system은 구조가 같아도 이름이 다르면 다른 타입으로 취급한다. 이러한 방식을 가진 대표적인 언어는 C와 Java이다.
type PersonID = string & { readonly brand: unique symbol };
function PersonID(id: string): PersonID {
return id as PersonID;
}
function getPersonById(id: PersonID) {}
getPersonById(PersonID('id-aaaaaa'));
getPersonById('id-aaaaaa'); // error: Argument of type 'string' is not assignable to parameter of type 'PersonID'.
// 무조건 PersonID를 사용해야 하는 경우, 그냥 문자열을 입력하면 에러 발생
타입은 호환성에 따라 서브 타입과 슈퍼 타입으로 나누어 볼 수 있다.
// sub1 타입은 sup1 타입의 서브 타입이다.
let sub1: 1 = 1;
let sup1: number = sub1;
sup1 = sub1
sub1 = sup1; // error
위와 같이 숫자 1 만을 넣을 수 있는 서브 타입 sub1은 모든 숫자를 담을 수 있는 슈퍼 타입 sup1에 넣을 수 있다. 그러나 그 반대는 에러를 발생시킨다.
// sub2 타입은 sup2 타입의 서브 타입이다.
let sub2: number[] = [1];
let sup2: object = sub2;
sub2 = sup2; // error
// sub3 타입은 sup3 타입의 서브 타입이다.
let sub3: [number, number] = [1, 2];
let sup3: number[] = sub3;
sub3 = sup3; //error
배열의 경우에도 객체의 일종이기 때문에 객체를 담을 수 있는 슈퍼 타입에 포함되며, 마찬가지로 튜플은 모든 배열을 담는 슈퍼 타입에 포함된다. 그러나 반대의 경우를 시도하면 에러를 발생시킨다.
// sub4 타입은 sup4 타입의 서브 타입이다.
let sub4: number = 1;
let sup4: any = sub4;
sub4 = sup4; // 정상작동
// sub5 타입은 sup5 타입의 서브 타입이다.
let sub5: never = 0 as never;
let sup5: number = sub5;
sub5 = sup5 // error
any를 이용하면 특이하게 어떠한 경우에도 에러가 발생하지 않으므로 사용이 자제된다. number와 같은 넓은 범위의 슈퍼 타입을 never나 기타 극단적인 경우에 넣으려고 해도 마찬가지로 에러가 발생하는 것을 볼 수 있다.
// sub6 타입은 sup6 타입의 서브 타입이다.
class Animal {}
class Dog extends: Animal {
eat() {}
}
let sub6: Dog new Dog();
let sup6: Animal = sub6;
sub6 = sup6; //error
더 넓은 범위의 Animal을 Dog에 할당하려 시도하면 에러가 발생한다. 이는 Animal에 eat() 이라는 메소드가 존재하지 않기 때문이다.
class Person {}
class Developer extends Person {
coding() {}
}
class StartupDeveloper extends Developer {
burning() {}
}
function tellme(f: (d: Developer) => Developer) {}
// Developer => Developer에 Developer => Developer 할당
tellme(function dToD(d: Developer): Developer {
return new Developer();
});
// Developer => Developer에 Person => Developer 할당
tellme(function pToD(d: Person): Developer {
return new Developer();
});
// Developer => Developer에 StartupDeveloper => Developer 할당
tellme(function sToD(d: StartupDeveloper): Developer {
return new Developer();
});
// 오류가 발생할 것 같지만 그대로 작동한다. 이러한 경우를 감지하는 옵션이 바로 strictFunctionTypes이다.
strictFunctionTypes 옵션을 이용하면 함수를 할당할 때 함수의 매개변수 타입이 같거나 슈퍼타입인 경우가 아닌 경우 에러를 발생시킨다.
type alias(별칭)는 interface와 비슷하게 직접 타입의 이름을 지정하는 것이다. 그러나 타입을 새로 만드는 것은 아니며, 만들어진 타입의 refer로 사용할 뿐이다.
let person1: string | number = 0;
person1 = 'Mark';
type StringOrNumber = string | number;
let person2: StringOrNumber = 0;
person2 = 'Anna';
// 타이핑을 줄이기 위해 사용할 수 있다.
type personTuple = [string, number];
let person3: personTuple = ['Mark', 35];
// 튜플에도 사용할 수 있다.
참고
패스트컴퍼스 - TypeScript Essentials