단언문 무조건 성공하는 법 : unknown을 포함해 단언하기. 모든 타입은 unknown의 서브타입임.
item10
string의 경우 기본형이지만 메서드를 가지는 객체 타입으로 정의되어 있기도 함. String.prototype
예를 들어 기본형 string을 String 객체로 wrapping하고, 메서드를 호출하고, 마지막에 wrapping한 객체를 버림 (몽키 패치).
몽키 패치란 런타임에 프로그램의 어떤 기능을 수정해서 사용하는 기법을 말함.
다른 기본형에도 동일하게 객체 래퍼 타입이 존재함. null, undifined 제외.
특징
타입스크립트가 제공하는 타입 선언은 전부 기본형 타입으로 되어 있어 타입 정의 시 기본형을 사용하면 됨.
new 없이 BigInt를 쓰면 기본형을 생성하지만 그래도 헷갈리니 기본형 쓰자.
item11
잉여 속성 체크가 할당 가능 검사와는 별도의 과정이라는 것을 알아야 타입스크립트 타입 시스템에 대한 개념을 정확히 잡을 수 있음.
객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여 속성 체크가 수행됨.
객체 리터럴 - 잉여 속성 체크
객체 리터럴 x - 잉여 속성 체크 x
임시 변수를 도입하면 잉여 속성 체크를 건너뛸 수 있음.
item12
function보다 const를 사용해 함수를 정의하자.
장점 1) 불필요한 코드의 반복을 줄임.
장점 2) 함수의 전체 타입을 정의하면 코드가 간결하고 안전해짐.
item13
type vs interface
공통점
잉여 속성 체크함.
인덱스 시그니처 사용 가능. ex) [key: string]: string
함수 타입 정의 가능.
제너릭 사용 가능.
인터페이스는 타입을, 타입은 인터페이스를 확장할 수 있음.
차이점
interface
type
유니온 x
유니온 o ex) type a = A | B
튜플 표현에 어려움 (하단 예시 참조)
튜플 표현이 쉬움
보강(선언 병합) 가능
보강 불가능
추가) 일반적인 타입 선언의 경우 type 혹은 interface 중에 컨벤션을 따르면 됨. 만약 api에 대한 타입 선언을 해야 한다면 인터페이스를 사용하는 게 좋음. api가 변경될 때 새로운 필드를 병합할 수 있기 때문. 하지만 프로젝트 내부적으로 사용되는 타입에 선언 병합이 발생하는 것은 잘못된 설계이기 때문에 이럴 경우 타입을 사용해야 함.
item14
타입도 반복을 줄여야 함. extends로 줄일 수 있음.
인덱싱으로 반복 줄이기
keyof를 사용해서 반복 줄이기
typeof를 사용해서 반복 줄이기
제너릭 타입을 이용해 반복 줄이기
item15
동적 데이터에 인덱스 시그니처를 사용하자.
csv 파일과 같은 형식을 인풋으로 받아 parsing 하는 함수가 있다고 할 때.
런타임 때까지의 객체 속성을 알 수 없을 경우.
인덱스 시그니처는 객체의 키, 밸류를 다음과 같이 표현한 것.
단점은 다음과 같음.
매핑된 타입을 사용하며 특정 키에 대해 다른 타입을 사용하기 원한다면 다음과 같이 할 수 있음.
item16
배열은 객체이므로 키는 숫자가 아닌 문자열임(js의 특성 때문).
Object.keys로 숫자로 구성된 배열의 key 값을 뽑으면 string 타입의 숫자가 출력됨.
인덱스 시그니처로 사용된 number 타입은 버그를 잡기 위한 순수 타입스크립트 코드임.
만약 string으로 배열을 찾고 싶다면 arrayLike를 사용하자.
item17
함수가 매개변수를 수정하지 않는다면 readonly로 선언하는 것이 좋음.
readonly 매개변수는 인터페이스를 명확하게 하며, 매개변수가 변경되는 것을 방지함.
readonly는 얕게 동작하기에 깊은 readonly 타입을 사용하고 싶다면 ts-essetials에 있는 DeepReadonly 제네릭을 활용하자.
item18
리액트에는 props가 변경될 때 해당 컴포넌트와 자식 컴포넌트가 리렌더링되는데, 이벤트 핸들러 같이 눈에 보이는 요소가 아니라면 리렌더링할 필요가 없음.
보수적 접근법 또는 실패에 닫힌 접근법
모든 props에 대해 변경을 감지함.
실패에 열린 접근법으로 작성을 해보자.
빈번한 렌더링은 막지만 정작 필요할 때 뷰가 바뀌지 않을 수 있음.
타입체커가 위와 같은 역할들을 대신하도록 만듦.
[k in keyof ViewProps]는 타입 체커에게 ViewProps와 동일한 속성을 가져야 한다는 것을 알려줌.
REQUIRES_UPDATE에 boolean 값을 가진 객체를 사용함. 나중에 추가 속성이 더해질때 REQUIRES_UPDATE에서 에러가 발생할 것임.
interface Person {
name: string;
}
interface Lifespan {
birth: Date;
death?: Date;
}
type PersonSpan = Person & Lifespan;
const ps: PersonSpan = {
name: 'Alan Turing',
birth: new Date('1912/06/23'),
death: new Date('1954/06/07'),
}; // OK
// 유니온은 앞의 교집합과 같이 동작하지 않음. 값의 key가 없기 때문.
type K = keyof (Person | Lifespan); // Type is never
// 다음과 생각하면 편함
keyof (A&B) = (keyof A) | (keyof B) // "name" | "birdh" | "death"
keyof (A|B) = (keyof A) & (keyof B) // never
// extends 키워드를 쓰는 것이 더 일반적임.
const triple: [number,number,number] = [1,2,3];
const double: [number,number] = triple;
// ~~~~ [nunber,number,number] 형식은
// [nunber,number] 형식에 할당할 수 없습니다.
// 'length'속성의 형식이 호환되지 않습니다.
// 3 형식은 2 형식에 할당할 수 없습니다.
interface Person {
first: string;
last: string;
}
const first: Person['first'] = p['first'];
// 타입선언(:) 또는 단언문(as) 다음에 나오는 심벌은 타입인 반면, = 다음에 오는 모든 것은 값이다.
type PersonEl = Person['first' | 'last']; // Type is string
type Tuple = [string, number, Date];
type TupleEl = Tuple[number]; // Type is string | number | Date
// !도 단언문임.
const elNull = document.getElementById('foo'); // Type is HTMLElement | null
const el = document.getElementById('foo')!; // Type is HTMLElement
const el = document.body as unknown as Person; // OK
// String은 자기 자신이 유일한 존재임.
> "hello" === new String("hello")
false
> new String("hello") === new String("hello") false
// 기본형에 할당하면 기존 속성이 사라짐.
> x = "hello"
> x.language = 'English' 'English'
> x.language
undefined
interface Room {
numDoors: number;
ceilingHeightFt: number;
}
const r: Room = {
numDoors: 1,
ceilingHeightFt: 10,
elephant: 'present',
// ~~~~~~~~~~~~~~~~~~ Object literal may only specify known properties,
// and 'elephant' does not exist in type 'Room' };
function add(a: number, b: number) { return a + b; }
function sub(a: number, b: number) { return a - b; }
function mul(a: number, b: number) { return a * b; }
function div(a: number, b: number) { return a / b; }
type BinaryFn = (a: number, b: number) => number;
const add: BinaryFn = (a, b) => a + b;
const sub: BinaryFn = (a, b) => a - b;
const mul: BinaryFn = (a, b) => a * b;
const div: BinaryFn = (a, b) => a / b;
// fetch 타입 정의하는 방법
declare function fetch(
input: RequestInfo, init?: RequestInit
): Promise<Response>;
// 함수 문장
async function checkedFetch(input: RequestInfo, init?: RequestInit) {
const response = await fetch(input, init);
if (!response.ok) {
// Converted to a rejected Promise in an async function
throw new Error('Request failed: ' + response.status); }
return 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;
}
type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];
interface Tuple {
0: number;
1: number;
length: 2;
}
const t: Tuple = [10, 20]; // OK
interface Person { firstName: string; lastName: string;
}
interface PersonWithBirthDate extends Person { birth: Date;
}
interface State {
userId: string;
pageTitle: string;
recentFiles: string[];
pageContents: string;
}
interface TopNavState {
userId: string;
pageTitle: string;
recentFiles: string[];
}
type TopNavState = {
userId: State['userId'];
pageTitle: State['pageTitle'];
recentFiles: State['recentFiles'];
};
type TopNavState = {
[k in 'userId' | 'pageTitle' | 'recentFiles']: State[k]
};
type Pick<T, K> = { [k in K]: T[k] };
type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>;
interface Options {
width: number; height: number; color: string; label: string;
}
interface OptionsUpdate {
width?: number; height?: number; color?: string; label?: string;
}
type OptionsUpdate = {[k in keyof Options]?: Options[k]};
type OptionsKeys = keyof Options;
// Type is "width" | "height" | "color" | "label"
const INIT_OPTIONS = { width: 640,
height: 480,
color: '#00FF00', label: 'VGA',
};
interface Options {
width: number; height: number; color: string; label: string;
}
You can do so with typeof:
type Options = typeof INIT_OPTIONS;
// 이 때 정의하고자 하는 타입이 함수 자체의 타입인지, 함수의 반환 타입인지 명확히 구별해야 함.
type UserInfo = ReturnType<typeof getUserInfo>;
interface Name { first: string; last: string;
}
type DancingDuo<T extends Name> = [T, T];
const couple1: DancingDuo<Name> = [
{first: 'Fred', last: 'Astaire'}, {first: 'Ginger', last: 'Rogers'}
]; // OK
const couple2: DancingDuo<{first: string}> = [
{first: 'Sonny'},
{first: 'Cher'}
];
// 추가) 제너릭 타입에서 매개변수를 제한할 수 있는 방법 : extends 사용하기
type Pick<T, K> = { [k in K]: T[k]
// ~ Type 'K' is not assignable to type 'string | number | symbol'
};
type Pick<T, K extends keyof T> = { [k in K]: T[k]
}; // OK
type Something = {[property: string]: string};
1. 이때 key는 string, number, symbol 중 하나가 되어야 한다.
2 인덱스 시그니처는 잘못된 키를 포함해 모든 키를 허용한다.
3.name 대신 Name 과 같이 키를 표현해도 유효한 타입으로 인정된다.
4.키마다 다른 타입을 가질 수 없다. name이 string이면서 index라는 키가 있다고 할 때 number타입으로 지정할 수 없다.
5.자동완성 기능이 동작하지 않는다.
type ABC = {[k in "a" | "b" | "c"]: k extends "b" ? string : number};
// Type ABC = {
// a: number;
// b: string;
// c: number;
// }
type Vec3D = Record<'x' | 'y' | 'z', number>;
// Type Vec3D = {
// x: number;
// y: number;
// z: number;
// }
type Vec3D = {[k in 'x' | 'y' | 'z']: number};
// Same as above
type ABC = {[k in 'a' | 'b' | 'c']: k extends 'b' ? string : number};
// Type ABC = {
// a: number;
// b: string;
// c: number;
// }
const tupleLike: ArrayLike<string> = {
"0": "A",
"1": "B",
length: 2,
}// 정상
function checkAccess<T>(xs: ArrayLike<T>, i: number): T {
if(i < xs.length) {
return xs[i];
}
}
function arraySum(arr: readonly number[]){
let sum = 0, num;
while((num = arr.pop()) !== undefined) { // 타입 에러
sum+=num;
}
return sum;
}