type OneDigitOdd = 1 | 3 | 5 | 7 | 9;
const three: OneDigitOdd = 3;
const num: number = three;
OneDigitOdd
타입이 가질 수 있는 값인 1
, 3
, 5
, 7
, 9
는 모두 number
에 속한다. 즉, OneDigitOdd
타입은 number
타입에 할당 가능(assignable) 하다.
반면, 아래의 코드처럼 4
는 OneDigitOdd
의 값에 해당하지 않으므로 할당이 불가능하다.
const four: number = 4;
const oneDigitOdd: OneDigitOdd = four;
// error TS2322: Type 'number' is not assignable to type 'OneDigitOdd'.
interface User {
name: string;
age: number;
}
interface Pet {
name: string;
species?: string;
}
const user: User = {name: '집사', age: 10};
const pet: Pet = {name: '나비'};
const pet2: Pet = user; (o)
const user2: User = pet; (x)
타입스크립트에서는 두 타입의 구조만을 비교하여 호환성을 결정한다.
User는 Pet 이 가지고 있는 name
을 갖기 때문에 호환이 가능하지만,
Pet은 User 가 가지고 있는 name
과 age
모두를 충족할 수 없기 때문에 호환이 불가능하다. 이렇게 동작하는 타입 시스템을 구조적 타입 시스템(structural type system)이라 부른다.
변수 white
는 Color
타입을 갖는다. 그리고 white
에 할당하려는 객체는 R
, G
, B
세 멤버를 모두 갖고 있고, 세 멤버 모두 number
타입이다. 따라서 구조적 타입 검사에 의하면 이 할당에는 아무런 문제가 없어야 하지만 A
는 error 를 발생시킨다.
그 이유는 객체 리터럴을 할당하는 경우에는 그 리터럴이 알려지지 않은 속성, 즉, A
와 같은 할당 받는 타입에 존재하지 않는 속성을 포함하면 타입 에러가 발생한다.
하지만, 아래 코드처럼 객체 리터럴이 아닌 변수를 할당하도록 바꾸면 에러는 사라진다.
이처럼 객체 리터럴에 알려지지 않은 속성이 있는지 추가적으로 시행하는 검사를 과잉 속성 검사(excess property checking)라 부른다.
과잉 속성 검사는 프로그래머의 실수를 막기 위해 존재한다. 어떤 타입의 객체 리터럴을 직접 할당하는 경우, 만약 해당 타입에 정의되지 않은 멤버는 오타 등의 실수로 인해 존재할 확률이 높다고 가정하는 것이다.
type Sum = (sumFirst: number, sumSecond: number) => number;
type Multiply = (mulFirst: number, mulSecond: number) => number;
모든 매개변수 타입이 number
로, 아래의 코드와 같이 서로 할당이 가능하다.
const sum: Sum = (sumFirst: number, sumSecond: number) => {
return sumFirst + sumSecond;
};
const multiply: Multiply = sum; // ok
하지만, 아래의 경우는 할당이 불가능하다.
interface Animal { animalProp: string };
interface Dog extends Animal { dogProp: number };
let f = (animal: Animal) => animal.animalProp;
let g = (dog: Dog) => dog.dogProp;
f = g; // error
type Login = (id: string) => string;
type LoginWithToken = (id: string, token: string) => string;
const loginWithToken: LoginWithToken = (id: string, token: string) => 'a';
const login: Login = loginWithToken;
loginWithToken
은 할당받는 함수 login
에 비해 token: string
이라는 매개 변수를 추가적으로 갖고 있다.
이런 경우는 할당이 불가능하다. 만약 이 할당을 허용한다고 생각한다고 생각해 보면, 아래와 같이 호출할 것이다.
login('myId');
이는 loginWithToken
함수를 token
인자 없이 호출하는 셈이다.
loginWithToken
함수 내에서 token
을 string
타입이라 생각하고 사용했다면, string
이 필요한 자리에 undefined
값이 넘어와 런타임 에러가 발생할 것이다.
따라서, 이런 할당은 허용되지 않는다.
const login: Login = (id: string) => 'a';
const loginWithToken: LoginWithToken = login;
이런 경우, 초과 매개변수는 무시된다.
그리고 매개변수 수가 같을 때와 동일한 알고리즘으로 호환성을 판단한다.
위의 경우, 초과 매개변수인 token: string
을 제외하고 첫 번째 매개변수는 동일한 타입을 가지므로 할당은 문제 없이 진행된다.
클래스의 호환성 비교는 객체 호환성 비교와 비슷하지만, 스태틱 멤버 및 생성자는 호환성 비교에 영향을 주지 않는다!!!
class Animal {
feet: number;
constructor (name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor (numFeet: number) { }
}
let a: Animal;
let s: Size = new Size(3);
a = s; // ok
s = a; // ok
public
멤버를 비교할 때에는 객체 속성을 비교할 때와 마찬가지로 이름이 같은지, 타입이 호환 되는지만 따진다. 하지만 private
멤버와 protected
멤버는 조금 다르게 처리된다. private
및 protected
속성은 이름이 같다고 해도 다른 클래스로부터 정의된 멤버라면 호환이 불가능하다.
class FacebookUser {
contructor (id: string, private password: string) {}
}
class TwitterUser {
constructor (id: string, private password: string) {}
}
let twitterUser: TwitterUser;
let facebookUser: FacebookUser;
twitterUser = facebookUser;
TwitterUser
타입과 FacebookUser
타입은 모두 private password: string
멤버를 갖는다. 비록 이름은 같지만 이 두 속성은 서로 다른 클래스에서 정의된 private
멤버다. 따라서 위와 같은 할당을 시도하면, 다음 타입 에러가 발생한다.
Type 'FacebookUser' is not
![스크린샷 2020-01-05 오후 1.26.59.png](https://velog.velcdn.com/post-images%2Feunn%2Fa2a4b830-2f73-11ea-a9bd-5da04f8bae2b%2F-2020-01-05-1.26.59.png)
assignable to type 'TwitterUser'.
Types have separate declarations of a private property 'password'.(2322)
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string> = {};
x = y; // ok
위의 경우 x
와 y
의 구조가 인수 타입를 차별화된 방식으로 사용하지 않기 때문에 호환 가능하다.
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
이 경우,
NotEmpty<number>
는 { data: number }
로NotEmpty<string>
은 { data: string }
으로number
에 string
을 할당하는 것은 불가능하다.let identity = function<T>(x: T): T {
// ...
};
let reverse = function<U>(y: U): U {
// ...
};
이 때, identity
와 reverse
함수의 타입에는 타입 변수가 남아 있다. 이럴 때에는 아직 남아있는 변수를 모두 any
타입으로 대체하고 호환성을 판단 한다.
아래와 같은 할당은 허용한다.
identity = reverse;
타입변수 T
를 any
로 대체한 (x: any) => any
와 타입 변수 U
를 any
로 대체한 (y: any) = > any
는 서로 할당 가능한 타입이기 때문이다.
enum Status { Ready, Waiting }
enum Color { Red, Blue, Green }
let status: Status = Status.Ready;
status = Color.Green; // error
number
에, 문자 열거형 값은 string
에 할당 가능하다.enum MyEnum {
Zero,
One = 1,
Name = '꽃'
}
const zero: number = MyEnum.Zero;
const one: number = MyEnum.One;
const name: string = MyEnum.Name;