Closed moonheekim0118 closed 2 years ago
타입스크립트의 심벌은 타입 공간이나 값 공간 중의 한 곳에 존재한다
타입 심벌과 변수 선언을 같은 단어로 하여도 관련은 없지만, 코드 상 오류가 발생할 수 있다.
타입 선언
타입 단언
타입 단언이 필요한 곳
자바스크립트의 기본형 값들에 대한 일곱가지 타입
string 기본형과 String 객체의 차이점
객체 레퍼타입이 있는 기본형 타입들
interface Options {
title: string;
darkMode?: boolean;
}
const o1 : Options = document;
const o2 : Options = new HTMLAnchorElement;
자바스크립트는 함수 문장과 함수 표현식을 다르게 인식한다
타입스크립트에서는 표현식 사용을 권장한다
type DiceRollFn = (sides:number) => number;
const rollDice: DiceRollFn = sides => { }
type BinaryFn = (a:number, b:number) => number;
interface State {
userId:string;
pageTitle:string;
recentFiles: string[];
pageContents:string;
}
interface TopNavState{
userId:string;
pageTitle:string;
recentFiles: string[];
}
interface TopNavState{
userId: State[userId];
pageTitle: State[pageTitle];
recentFiles: State[RecentFiles];
}
type TopNavState = {
[ k in 'userId' | 'pageTitle' | 'recentFiles' ] : State[k];
}
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles' >;
type Pick<T, K> = {
[k in K]: T[k]
}
// K 타입은 'string | number | symbol ' 타입에 할당 할 수 없습니다.
type Pick<T, K extends keyof T> = {
[k in K]: T[k]
}
interface Array<T>{
// ...
[ n:number] : T
}
이렇게 말이다.
interface ArrayLike<T> {
readonly length: number
readonly [n: number]: T
}
타입이 명시된 변수에 객체 리터럴을 할당할 때 타입스크립트는 해당 타입의 속성이 있는지 그리고 그 외의 속성은 없는지
확인한다.
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
const obj = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'wow'
};
const r: Room = obj; // 정상
const room: Room = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'wow'
}
// Type '{ numDoors: number; ceilingHeightFt: number; elephant: string; }'
// is not assignable to type 'Room'.
두번째 예시에서는 잉여 속성 체크 과정이 수행되었다. 하지만 첫번째 예시에서는 아니다. 이는 잉여 속성 체크가 할당 가능 검사와는 별도의 과정이기 때문이다.
const o = { darkmode: true, title: 'Sky' } as Options; // 정상
잉여 속성 체크는 타입 단언문을 사용할 때도 적용되지 않는다. 이는 단언문보다 선언문을 써야하는 또 다른 이유이다.
타입스크립트에서는 함수의 매개변수부터 반환값까지를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있기 때문에 함수 표현식을 사용하는 것이 좋다.
type DiceRollFn = (sides: number) => number;
const rollDice: DiceRollFn = sides => { ... }
함수 타입의 선언은 불필요한 코드의 반복을 줄인다.
라이브러리는 공통 함수 시그니처를 타입으로 제공하기도 한다. 예를 들어 리액트는 함수 전체에 적용할 수 있는 MouseEventHandler 타입을 제공한다.
다른 함수의 시그니처를 참조하려면 typeof fn을 사용하면 된다.
대부분의 경우에는 타입을 사용해도 되고 인터페이스를 사용해도 된다. 그러나 이 차이를 분명하게 알고, 같은 상황에서는 동일한 방법으로 타입을 정의해 일관성을 유지하자.
interface IState {
name: string;
capital: string;
}
interface IState {
population: number;
}
const dory: IState = {
name: 'dory',
capital: 'park',
population: 1000
}
타입 중복은 코드 중복만큼 많은 문제를 발생시킨다.
반복을 줄이는 간단한 방법은 타입에 이름을 붙이는 것이다. 상수를 사용해서 반복을 줄이듯이 중복된 타입에 이름을 붙여주자.
인터페이스가 다른 인터페이스를 확장하게 해서 반복을 제거할 수 있다. 이때 인터섹션 연산자(&)를 쓸 수도 있다.
interface State{
userId: string;
pageTitle: string;
recentFiles: string[];
pageContentes: string;
}
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[];
}
다음과 같이 State의 부분집합인 TopNavState를 구현하고자 한다면 어떻게 타입 중복을 줄일 수 있을까?
type TopNavState = {
userId: State['userId']
pageTitle: State['pageTitle']
recentFiles: State['recentFiles']
}
State를 인덱싱하여 속성의 타입에서 중복을 제거할 수 있다. 중복되는 코드를 더 줄여보자. 매핑된 타입을 사용하면 된다.
type TopNavState = {
[k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
}
이런 맵핑된 타입을 사용해서 표준 라이브러리에서 Pick을 구현해 놓았다!
type Pick<T, K> = {[k in K]: T[k]}
Pick은 제너릭 타입이다. 마치 함수에서 두개의 매개변수 값을 받아서 결과값을 반환하는 것처럼, Pick은 T와 K 두가지 타입을 받아서 결과 타입을 반환한다.
값의 형태를 따르는 타입을 정의해 주고 싶을 때는 typeof를 사용하면 된다. 다만 이때는 타입 정의를 먼저 하고 값이 그 타입에 할당 가능하다고 선언하는 것이 좋다.
함수에서 매개변수로 들어갈 수 있는 값을 제한하기 위해 타입 시스템을 사용하는 것처럼 제너릭 타입에서 매개변수를 제한할 수 있는 방법이 필요하다. 이때 extends
를 사용한다.
type Pick<T, K extends keyof T> = {[k in K]: T[K]}
extends가 부분집합을 표현함을 다시 생각하자. 결론적으로 잘못된 키를 넣으면 오류가 발생하게 된다.
제너릭은 타입을 위한 함수와 같다.
제너릭 타입에서 매개변수를 제한할 때는 extends
를 사용한다. extends
는 제너릭 매개변수가 특정 타입을 확장한다고 선언할 수 있다.
Pick
, Partial
, ReturnType
과 같이 표준 라이브러리에 정의한 제너릭 타입에 익숙해지자.
State의 부분 집합인 타입을 정의해보자.
interface State {
userId: string;
pageTitle: string;
recentFiles: string[];
pageContents: string;
}
// 아래의 방법은 State 속성의 타입이 바뀌면 따라서 일일이 속성 타입을 바꿔줘야 한다.
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[];
}
// State를 인덱싱하여 속성 타입의 중복을 제거할 수 있다.
type TopNavState = {
userId: State['userId'];
pageTitle: State['pageTitle'];
recentFiles: State['recentFiles'];
}
// 매핑된 타입을 사용하여 중복을 조금 더 제거해보자.
// 매핑된 타입은 배열의 필드를 루프 도는 것과 같은 방식이다.
type TopNavState = {
};
// Pick을 사용하여 위의 패턴을 사용할 수 있다.
// Pick은 T와 K 두 가지 타입을 받아서 결과 탕비을 반환하는 제너릭 타입이다.
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
유니온을 인덱싱하여 타입 반복 없이 타입을 정의할 수 있다.
interface SaveAction {
type: 'save';
}
interface LoadAction {
type: 'load';
}
type Action = SaveAction | LoadAction;
type ActionType = Action['type']; // "save" | "load"의 타입
type ActionRec = Pick<Action, 'type'>; // {type: "save" | "load"}
// Pick을 사용하면, 'type'이라는 속성을 가지는 인터페이스가 만들어진다.
매핑된 타입과 keyof
를 사용하면, 타입으로부터 내부 속성이 모두 선택적 필드인 다른 타입을 만들 수 있다.
keyof
는 타입을 받아서 속성 타입의 유니온을 반환한다.
interface Options {
width: number;
height: number;
color: string;
label: string;
}
type OptionsUpdate = { [k in keyof Options]?: Options[k] }
type OptionsKeys = keyof Options; // 타입이 "width" | "height" | "color" | "label"
값의 형태에 해당하는 타입을 정의하고 싶을 때는 typeof
를 사용한다.
함수나 메서드의 반환 값에 명명된 타입을 만들고 싶을 때는 ReturnType
제너릭을 사용할 수 있다.
function getUserInfo(userId: string) {
// ...
return {
userId,
name,
age
}
}
// 추론된 반환 타입은 { userId: string, name: string, age: string }
type UserInfo = ReturnType<typeof getUserInfo>;
추석특집 이펙티브 타입스크립트 순항중~ 포항항ꉂꉂ(ᵔᗜᵔ*) 🛳🌊