// named type
type State = {
name: string;
capital: string;
};
// interface
interface State {
name: string;
capital: string;
}
// Index Signature 사용도 둘 다 가능하다.
type TDict = { [key: string]: string };
interface TDict {
[key: string]: string;
}
type TFn = (x: number) => string;
interface TFn {
(x: number): string;
}
// 인터섹션 혹은 extends 를 통해 확장이 가능하다.
interface TDictWithPop extends TState {
population: number;
}
type TStateWithPop = TDict & {
population: number;
};
// 타입 및 인터페이스를 implements 키워드를 통해 적용할 수 있다.
class StateT implements TState {
name: string = '';
capital: string = '';
}
&
연산자를 사용하자.type A = { a: 'a' };
type B = { b: 'b' };
// interface 에서는 구현이 불가능한 타입 연산이다.
type ABName = (A | B) & { name: string };
type
키워드는 일반적으로 interface
보다 쓰임새가 다양하며 유니온 타입, 매핑된 타입, 조건부 타입 infer
등 다양한 기능에 사용 가능하다.type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]]; // 0번째 인덱스에 string을 요구
// 비슷하게 정의는 가능하지만, 이는 유사 배열 객체를 의미한다.
interface SimilarArray {
0: number;
1: number;
length: 2;
}
type
키워드를 통해 더욱 간결하게 표현할 수 있다.interface IState {
name: string;
capital: string;
}
// 같은 Interface를 한 차례 더 선언하여 병합시킴.
interface IState {
population: number;
}
const wyoming: IState = {
name: 'Wyoming',
capital: 'Cheyenne',
population: 500000,
};
interface Point2D {
x: number;
y: number;
}
// 타입 적용 전, 중복된 객체 리터럴 타입이 보인다.
function distance(a: { x: number; y: number }, b: { x: number; y: number }) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function distance(a: Point2D, b: Point2D) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function get(url: string, opts?: Options): Promise<Response> {
/**...*/
}
function post(url: string, opts?: Options): Promise<Response> {
/**...*/
}
// 명명된 타입으로 이를 분리하여 하나의 함수 타입을 공유하도록 함
type HTTPFunction = (url: string, opt?: Option) => Promise<Response>;
const get: HTTPFunction = (url, opts) => {
/**...*/
};
const post: HTTPFunction = (url, opts) => {
/**...*/
};
&
를 사용하고, 인터페이스의 경우 extends
키워드를 쓰자.interface State {
userId: string;
pageTitle: string;
recentFiles: string[];
pageContents: string;
}
// TopNavState를 확장하여 State를 구성하였다.
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[];
}
// State의 부분 집합으로서 TopNavState를 구성하였다.
// State를 indexing 하여 속성 타입 (string) 의 중복을 제거할 수 있다.
type TopNavState = {
userId: State['userId'];
pageTitle: State['pageTitle'];
recentFiles: State['recentFiles'];
};
// 이를 Mapped Type 으로 묶어 표현하면 더욱 간결해진다.
type Pick<T, K> = { [key in K]: T[key] };
type TopNavState = {
[key in 'userId' | 'pageTitle' | 'recentFiles']: State[key];
}; // = Pick<State, keyof State>
{[P in K] : T}
이며, Pick<T, K>
유틸 타입을 사용하여 두 가지 타입을 제네릭 인자로 받아 결과로 반환 받을 수 있다.interface SaveAction {
type: 'save';
}
interface LoadAction {
type: 'load';
}
type Action = SaveAction | LoadAction;
// type ActionType = 'save' | 'load'; // 타입의 반복. 좋지 않음
type ActionType = Action['type']; // 기존의 타입을 재활용 했기에 좋음.
type ActionType = Pick<Action, 'type'>; // 상단의 타입과 동일하게 작동함.
interface Options {
width: number;
height: number;
color: number;
label: number;
}
// 굳이 새로운 타입을 이렇게까지 만들어야 할까? 아니다.
interface OptionsUpdate {
width?: number;
height?: number;
color?: number;
label?: number;
}
// 매핑된 타입과 keyof 키워드를 사용하여 간단하게 타입 제작이 가능하다.
type KeyofOptions = keyof Options; // 'width' | 'height' | 'color' | 'label'
type OptionsUpdate = { [key in keyof Options]?: Options[key] };
keyof
연산자는 타입을 받아 해당 타입의 속성들을 유니온 타입으로 묶어 반환시킨다.?
연산자는 해당 속성을 Optional 하게 만든다. 만약 Optional 속성을 제거하고 싶다면 반대로 -?
를 사용하자.readonly
연산자는 해당 속성을 read-only 하게 만든다. 만약 해당 속성을 제거하고 싶다면 반대로 -readonly
를 사용하자.const INIT_OPTIONS = {
width: 640,
height: 480,
color: '#00FF00',
label: 'VGA',
};
type initOptions = typeof INIT_OPTIONS;
typeof
연산자를 사용해라.typeof
는 타입의 영역에서 사용될 경우 해당 값을 그대로 리터럴 타입으로서 변환한 후 적용시킨다.interface Name {
first: string;
last: string;
}
type DancingDuo<T extends Name> = [T, T]; // T는 Name의 부분집합
// 적절한 타입을 대입했기에 통과되었다.
const couple1: DancingDuo<Name> = [
{ first: 'Fred', last: 'Astaire' },
{ first: 'Ginger', last: 'Rogers' },
];
// 매개변수 T 는 Name의 부분집합인데, last 속성이 없어 오류를 발생시킨다.
const couple2: DancingDuo<{ first: string }> = [
{ first: 'Fred', last: 'Astaire' },
{ first: 'Ginger', last: 'Rogers' },
];
extends
키워드를 사용하는 것이다.extends
키워드를 사용하면 제네릭 매개변수가 특정 타입을 확장한다고 선언하는 것과 같다.Pick
, Omit
, Partial
같은 유틸 타입도 사용하는 것이 좋다.type Rocket = { [prop: string]: string };
const rocket: Rocket = {
name: 'Falcon 9',
variant: 'v1.0',
thrust: '4,940 kN',
};
여기서 나오는 [props: string]: string
이 바로 인덱스 시그니쳐 이며, 이는 아래와 같은 뜻을 담고 있다.
string
, number
, symbol
의 조합이어야 한다. 보통은 string
을 쓴다.type Rocket = { [prop: string]: string };
const rocket: Rocket = {
mame: 'Falcon 9', // name 이 아닌 mama 이어도 허용된다.
variant: 'v1.0',
thrust: '4,940 kN',
};
const rocket2: Rocket = {
mame: 'Falcon 9', // thrust 속성이 누락되었음에도 허용된다.
variant: 'v1.0',
};
const empty: Rocket = {} // 빈 객체여도 허용된다.
// 정확하긴 하지만 너무 길어서 사용하기가 번거롭다.
type Row3 =
| { a: number }
| { a: number; b: number }
| { a: number; b: number; c: number }
| { a: number; b: number; c: number; d: number };
// Record 유틸 함수를 사용하면 간단하게 표현이 가능하다.
type Row3 = Record<'a' | 'b' | 'c' | 'd', number>;
// 매핑된 타입을 사용하는 것도 가능하다.
type Row3 = {
a: number;
[key in 'b' | 'c' | 'd']? : number;
};
Record
유틸 함수나 매핑된 타입을 쓰자.const obj = {
1: 2,
3: 4, // 두 속성은 모두 문자열로 변환된다.
};
const arr = [1, 2, 3, 4];
arr['1']; // 정상, 배열도 객체이기에 index 속성으로 접근이 가능하다. (TS에서는 불가)
// TS 에서는 숫자 키를 허용한다. Array 에 대한 타입 선언에 쓰인다.
interface Array<T> {
[n: number]: T;
}
// 만약 키 타입이 number 지만 배열과 비슷한 형태를 쓸 경우, ArrayLike를 쓰자.
// Array 타입으로 지정될 경우 사용하지 않을 push, concat 같은 속성도 불러오게 된다.
function checkedAccess<T>(xs: ArrayLike<T>, i: number): T {
if (i<xs.length>) return xs[i];
throw new Error('OutOfIndexRangeError');
}
string
타입 대신 number
를 타입의 인덱스 시그니쳐로 사용할 경우 TS는 Array
혹은 Tuple
타입을 대신 사용할 것이다.ArrayLike
타입을 사용하자.function printTriangles(n: number) {
const nums = [];
for (let i = 0; i < n; i++) {
nums.push(i);
console.log(arraySum(nums));
}
}
// 아래와 같이 함수를 정의하면 요소의 합을 구할 수 있다.
// 하지만 계산이 끝나면 원래 배열이 비게 된다는 문제가 발생한다.
function arraySum(arr: number[]) {
let sum = 0,
num;
while ((num = arr.pop()) !== undefined) [(sum += num)];
return sum;
}
readonly
접근 제어자는 요소를 수정할 수 없게끔 막는 역할을 한다.// 배열을 수정하지 않으면서, 반복을 통해 요소의 합을 구할 수 있다.
function arraySum(arr: readonly number[]) {
let sum = 0;
for (const num of arr) {
sum += num;
}
return sum;
}
readonly
접근 제어자를 사용하여 변경하지 않음을 기술하는 것이 더욱 좋다.type T = { readonly inner: { x: number } };
const o: T = { inner: { x: 0 } };
// 허용됨, readonly 는 오직 inner 속성에만 해당되기 때문. 내부의 x는 아님.
o.x = 1;
readonly
는 얕게 동작하기 때문에 이를 항상 유의해야 한다.ts-essentials
라이브러리의 DeepReadonly
제네릭으로 이를 해결할 수는 있다.interface ScatterProps {
xs: number[];
ys: number[];
xRange: [number, number];
yRange: [number, number];
color: string;
onClick: (x: number, y: number, index: number) => void;
}
// 보수적 접근법 : 정확하게 동작하나 너무 자주 그려질 가능성이 있음.
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
let k: keyof ScatterProps;
for (k in oldProps) {
if (oldProps[k] !== newProps[k]) {
if (k !== 'onClick') return true;
}
}
}
// 매핑된 타입을 통해 객체를 생성하고, 이를 활용한다.
const REQUIRES_UPDATE: { [key in keyof ScatterProps]: boolean }; = {
xs: true,
ys: true,
xRange: true,
yRange: true,
color: true,
onClick: false
}
function shouldUpdate(oldProps: ScatterProps, newProps: ScatterProps) {
let k: keyof ScatterProps;
for (k in oldProps) {
// 반드시 같아야 하는 속성을 객체로서 정의하여 매핑된 타입을 적용하였다.
if (oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]) {
return true;
}
}
}
// 다음과 같이 as 키워드를 사용하여 re-mapped types 기법을 활용할 수도 있다.
type RequireUpdateOptions = { [key in keyof ScatterProps as key extends 'onClick' ? never : key]: true };