객체들을 조합하여 더 크고 복잡한 객체를 만드는 방법과 유사하게 TS에 타입으로 이를 수행하는 도구가 있다. 여러가지 타입을 이용하여 새 타입을 작성하기 위해 일상적인 코드에서 가장 많이 사용되는 코드는 유니언과 제네릭이다.
유니언은 타입이 여러 타입 중 하나일 수 있다는 것을 선언한다.
유니언은 다양한 타입을 처리하는 방법을 제공한다. 가장 많이 사용 되는 사례 중 하나는 값이 다음과 같이 허용되는string
또는number
의 리터럴 집합을 설명하는 것이다.
리터럴 집합
리터럴 타입은 집합 타입의 보다 구체적인 하위 타입. "Hello World"는 string이지만, string은 "Hello World"가 아니다~
리터럴 타입 좁히기
let, var로 선언한건 값이 변할 수 있다고 컴파일러에게 알린다. 하지만 const로 선언한 변수는 변하지 않는다고 알린다.
문자열 리터럴 타입
실제로 문자열 리터럴 타입은 유니언 타입, 타입 가드 그리고 타입 별칭과 잘 결합된다. 이런 기능을 함께 사용하여 문자열로 enum (열거형: 서로 연관된 상수들의 집합) 과 비슷한 형태를 갖출 수 있다. 오버로드를 구별하는 것과 동일한 방법으로 사용될 수 있다.
숫자형 리터럴 타입
TS 에서 위의 문자열 리터럴과 같은 역할을 하는 숫자형 리터럴 타입도 있다.
주로 설정값을 설명할 때 사용.
type WindowStates = 'open'|'closed'|'minimized';
type LockStates = 'locked'|'unlocked';
type OddNumberUnderTen = 1 | 3 | 5 | 7 | 9;
또는 array
, string
을 받는 함수가 있을 수 있다.
function getLength(odj: string | string[]) {
return obj.length;
}
type Mybool = true | false;
//마우스를 올리면 bool 타입으로 분류된다.
(구조적 타입 시스템의 프로퍼티)
number
이나 string
을 매개변수로 기대하는 라이브러리를 사용할 때가 있다. function padLeft(value: string, padding: any) {
// 문자열을 받고 왼쪽에 padding을 추가한다.
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value;
// 만약 'padding'이 문자열이라면, 'padding'은 왼쪽에 더해진다.
}
if (typeof padding === 'string') {
return padding + value;
// 만약 'padding'이 숫자라면 그 숫자만큼 공백이 왼쪽에 더해질 것이다.
}
throw new Error(`Expected string or number, got '${padding}'.`)
}
console.log(padLeft('Hello world', 10)); // ' Hello world'출력
declare function padLeft2(value: string, padding: any): string;
let indentedString = padLeft2('Hello world', true); // 컴파일 타임은 통과, 런타임 오류
function padLeft3(value: string, padding: string | number) {
// ...
} // 여러 타입중 하나가 될 수 있다는 뜻. 값의 타입이 number, string 혹은 boolean이 될 수 있음을 의미
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
latEggs(): void;
}
declare function getSmallPet(): Fish | Bird;
let pet = getSmallPet();
pet.layEggs();
pet.swim();
// 두 개의 잠재적인 타입 중 하나만 사용할 수 있다.
type NetworkLoadingState = {
state: 'loading';
}
type NetworkFailedState = {
state: 'failed';
code: number;
}
type NetworkSuccessState = {
state: 'success';
response: {
title: string;
duration: number;
summary: string;
};
};
type NetworkState = | NetworkLoadingState | NetworkFailedState | NetworkSuccessState;
// 모두 state 라는 필드를 갖고 있다.
// state 필드가 공통으로 존재한다는 점을 안다면, 존재 여부를 체크하지 않고도 접근할 수 있다.
state
를 가지고 있다면, state
의 값은 대응하는 동일한 문자열과 대조되고 TS는 현재 어떤 타입이 사용되고 있는지 알 것이다.switch
문을 사용 한다.function networkStatus(state: NetworkState): string {
switch (state.state) {
case 'loading': return 'Downloading...';
case 'failed': return `Error ${state.code} downloading`;
case 'success': return `Downloaded ${state.response.title} = ${state.response.summary}`
}
}
interface ErrorHandling {
success: boolean;
error?: { message: string }
}
interface ArtworksData {
artworks: { title: string }[];
}
interface ArtistsData {
artists: { name: string }[];
}
type ArtworksResponse = ArtworksData & ErrorHandling;
type ArtistsResponse = ArtistsData & ErrorHandling;
const handleArtistsResponse = (response: ArtistsResponse) => {
if (response.error) {
console.log(response.error.message);
return;
}
console.log(response.artists);
};
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(name: string): void;
}
class ConsoleLogger implements Loggable {
log(name: string) {
console.log(`Hello I'm ${name}.`);
}
}
// 두 객체를 받아 하나로 합친다.
function extend<First extends {}, Second extends {}>(
first: First,
second: Second
): First & Second {
const result: Partial<First & Second> = {};
for (const prop in first) {
if (first.hasOwnProperty(prop)) {
(result as First)[prop] = first[prop];
}
}
for (const prop in second) {
if (second.hasOwnProperty(prop)) {
(result as Second)[prop] = second[prop];
}
}
return result as First & Second;
}
const jim = extend(new Person('Jim'), ConsoleLogger.prototype);
jim.log(jim.name);
type StringArray = Array<string>;
type NumberArray = Array<number>;
type ObjectWithNameArray = Array<{ name: string }>;
(backpack 변수가 string이므로, add 함수에 number를 전달할 수 없음)
echo
명령과 비슷함)function identity(arg: number): number {
return arg;
}
// 제네릭이 없다면 특정 타입을 주어야함
any
타입을 사용해서 기술 할 수 있는데, 이는 어떤 타입이든 받을 수 있다는 점이 제네릭이지만, 실제 함수가 반환할 때 어떤 타입인지 정보는 잃는다. function identity<T>(arg: T): T {
return arg;
}
// 두 가지 방법으로 호출 할 수 있다.
let output = identity<string>('myString);
// 함수에 타입 인수를 포함한 모든 인수를 전달한다.
console.log(output); //'myString'
let output = identity('myString')
// 전달 하는 인수에 따라 컴파일러가 T의 값을 자동으로 정하게 함(타입 인수 추론)
<>
이 괄호안에 표기한다.function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
function loggingIdentity<T>(arg: T[]): T[]
배열로 만들면 된다.function identity<T>(arg: T): T {
console.log(arg);
return arg;
}
let myIdnetity: <T>(arg: T) => T = identity;
```typescript
- 타입 매개변수에 다른 이름을 사용 할 수 있다. `let myIdentity: <U>(arg: U) => U = identity;`
- 객체 리터럴 방식 `let myIdentity: { <T>(arg: T): T } = identity;`
- 제네릭 인터페이스
```typescript
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
interface GenericIdentityFn<T> {
// 달라잔 점! <T>
(arg: T): T;
}
//...생략...
let myIdentity: GenericIdentityFn<number> = identity; // <타입> 추가
<>
를 붙이고 안에 타입 매개변수를 전달한다. 클래스 블럭 구문에는 타입 매개변수 목록들을 가진다.class GenericNumber<T> {
zeroValue: T,
add: (x: T, y: T)
=> T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; }
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
length
프로퍼티를 가진 타입으로 제약 조건을 걸고 싶을때?extends
키워드로 인터페이스를 사용하면 된다.interface Lengthwise {
length: number;
}
function loggingIndentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length) // 이제 .length 프로퍼티가 있는 것을 알기 때문에 더 이상 오류가 발생하지 않습니다.
return arg;
}
loggingIndentity({ length: 10, value: 3 });
obj
에 존재하지 않는 프로퍼티를 가져오지 않게 제약을 건다.function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, 'a');
getProperty(x, 'm'); // 오류
K
를 T
key타입(keyof
)으로 제한하므로서 실수를 방지할 수 있다.function create<T>(c: {new(): T; }): T {
return new c();
}
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // 타입검사!
createInstance(Bee).keeper.hasMask; // 타입검사!