TS에서는 함수 표현식을 사용하는 것이 좋다.
함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 => 표현식에 재사용가능하다.
function rollDice1(sides: number): number { /* ... */ } // Statement
const rollDice2 = function(sides: number): number { /* ... */ }; // Expression
const rollDice3 = (sides: number): number => { /* ... */ }; // Also expression
함수의 매개변수의 타입만 정의하는 것이 아니라 함수 표현식 전체 타입을 정의하는 것이 코드도 간결하고 안전하다.
아래와 같이 매개변수의 type 만 명시해줘도 정상적으로 동작한다. 하지만 return type에 대한 정의가 없기 때문에 throw를 return으로 바꿨을 때 에러를 잡아내지 못한다.
async function checkedFetch(input: RequestInfo, init?: RequestInit) {
const response = await fetch(input, init);
if (!response.ok) {
// An exception becomes a rejected Promise in an async function.
throw new Error(`Request failed: ${response.status}`); // 만약 이 부분이 return이라면?
}
return response;
declare function fetch(
input: RequestInfo, init?: RequestInit,
): Promise<Response>;
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response;
}
대부분은 비슷한점이 많다.
class를 구현할 때 type과 인터페이스 모두 구현이 가능하다.
class StateT implements TState {
name: string = '';
capital: string = '';
}
class StateI implements IState {
name: string = '';
capital: string = '';
}
차이점
인터페이스는 타입을 확장 할 수는 있지만 유니언을 할 수는 없다. => type써서 유니온 해야한다.
튜플, 배열타입은 type을 쓰는게 더 낫다.
type Pair = [a: number, b: number];
type StringList = string[];
type NamedNums = [string, ...number[]];
mapped type 기법 : 배열의 필드를 루프 도는 것과 같은 방식이다.
아래와 같은 타입이 있을 때 => State Type에 매핑된 타입을 사용하면 중복을 제거 할 수 있다.
Omit도 사용가능.
interface State {
userId: string;
pageTitle: string;
recentFiles: string[];
pageContents: string;
}
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[];
// omits pageContents
}
interface TopNavState {
userId: State['userId'];
pageTitle: State['pageTitle'];
recentFiles: State['recentFiles'];
}; //better
type TopNavState = {
[K in 'userId' | 'pageTitle' | 'recentFiles']: State[K]
}; // best
type Pick<T,K extends keyof T> = { [k in K] : T[K] };
interface SaveAction {
type: 'save';
// ...
}
interface LoadAction {
type: 'load';
// ...
}
type Action = SaveAction | LoadAction;
type ActionType = 'save' | 'load'; // Repeated types!
type ActionType = Action['type']; // best, 'save'| 'loading'
interface Options {
width: number;
height: number;
color: string;
label: string;
}
interface OptionsUpdate {
width?: number;
height?: number;
color?: string;
label?: string;
}
class UIWidget {
constructor(init: Options) { /* ... */ }
update(options: OptionsUpdate) { /* ... */ }
}
type OptionsUpdate = { [k in keyof Options]? : Options[k] }
function getUserInfo(userId: string) {
// ...
return {
userId,
name,
age,
height,
weight,
favoriteColor,
};
}
// Return type inferred as { userId: string; name: string; age: number, ... }
type UserInfo = ReturnType<typeof getUserInfo>;
type Rocket = {[property: string]: string};
const rocket: Rocket = {
name: 'Falcon 9',
variant: 'v1.0',
thrust: '4,940 kN',
}; // OK
- 잘못된 키를 포함해 모든 키를 허용함. (string 타입이기만 하면됨)
- 특정키가 필요하지 않음.( {} 도 유효한 Rocket type이다)
- key마다 다른 타입을 가질 수 없다. 예를들어 thrust는 string이 아니라 number 여야 할 수도 있다.
- 자동완성 기능이 동작하지 않는다.
이런 단점들이 있기 때문에 타입을 단언 할 수 있을때는 인덱스 시그니처 사용을 지양하자. (동적으로 추가할때만 사용하자)
어떤 타입에 가능한 필드가 제한되어 있는 경우라면 인덱스 시그니처로 모델링 하면 안된다.
데이터에 a,b,c,d와 같은 key가 존재하지만 얼마나 많은지 모른다면? => Record를 사용하자. key값을 제한가능하다.
interface Row1 { [column: string]: number } // Too broad
interface Row2 { a: number; b?: number; c?: number; d?: number } // Better
type Row3 =
| { a: number; }
| { a: number; b: number; }
| { a: number; b: number; c: number; }
| { a: number; b: number; c: number; d: number }; // Also better
type Vec3D = Record<'x' | 'y' | 'z', number>;
// ^? type Vec3D = {
// x: number;
// y: number;
// z: number;
// }
type ABC = {[k in 'a' | 'b' | 'c'] : k extends 'b'? string : number}
const test:ABC = {
'a': 1,
'b': '2',
'c' : 3,
}
type KeyTypes = {
a: string;
b: number;
c: boolean;
};
type MyObject = {
[K in keyof KeyTypes]: KeyTypes[K];
};
- 런타임때까지 객체의 속성을 알 수 없을 때만 인덱스 시그니처를 사용하자
배열은 객체이다 => 그러므로 key값은 string인데, TS에서는 인덱스 시그니쳐로 버그를 잡기 위해 number형을 사용한다.
인덱스 시그니처에 number를 사용하기 보다 Array나 튜플, ArrayLike Type을 사용하는게 좋다.
interface Array<T> {
// ...
[n: number]: T;
}
const xs = [1, 2, 3];
const x0 = xs[0]; // OK
const x1 = xs['1']; // stringified numeric constants are also OK
const inputEl = document.getElementsByTagName('input')[0];
const xN = xs[inputEl.value];
// ~~~~~~~~~~~~~ Index expression is not of type 'number'.
const tupleLike: ArrayLike<string> = {
'0': 'A',
'1': 'B',
length: 2,
}; // OK
다음과 같은 코드가 있다고 가정하자.
arraySum에서 원본 배열을 변경해 문제가 발생 => 이럴땐 readonly 키워드를 사용(원본배열을 변경하지 못하도록)
원본을 변경하지 못하도록 하는 키워드이다.
function printTriangles(n: number) {
const nums = [];
for (let i = 0; i < n; i++) {
nums.push(i);
console.log(arraySum(nums));
}
} // num 에 push
function arraySum(arr: number[]) {
let sum = 0, num;
while ((num = arr.pop()) !== undefined) {
sum += num;
}
return sum;
} // pop을 하기 때문에 => 이전의 수들이 유지가 되지 않음.
>printTriangles(5)
0
1
2
3
4
기본적으로 readonly는 얕게 동작한다. Readonly 제너릭에도 동일하게 적용됨.
inner에 적용되는 것이지 x에 적용되는게 아니다. 깊은 readonly를 사용하려면 라이브러리를 사용하는게 낫다.
type T = Readonly<Outer>;
// ^? type T = {
// readonly inner: {
// x: number;
// };
// }