- typeof null === ‘object’
- null, undefined는 strictNullChecks 여부에 따라 달라짐.
- never 타입으로 선언된 변수의 범위는 공집합이기 때문에 아무런 값도 할당할 수 없음.
ex) const x: never = 12; 는 불가능함.
- 교집합 시 모든 걸 포함함.
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 키워드를 쓰는 것이 더 일반적임.
- 그림 2-7 그림 참고
- 타입은 엄격한 상속 관계가 아니라 겹쳐지는 집합으로 표현함.
extends K, T
function getKey<K extends string>(val: any, key: K){ //... } function sortBy<K extends keyof T, T>(vals: T[], key: K): T[]{ //... } interface Point { x:number; y:number; } const pts: Point[] = [{x: 1, y: 1},{x: 2, y: 0}]; sortBy(pts, 'x'); // 정상. 'x'는 'x'|'y'를 상속 (즉, keyof T) sortBy(pts, 'y'); // 정상. 'y'는 'x'|'y'를 상속 sortBy(pts,Math.random() < 0.5 ? 'x' : 'y'); // 정상, 'x'|'y' 는 'x'|'y'를 상속 sortBy(pts,'z'); // ~~~ "z" 형식의 인수는 "x' | 'y' 형식의 매개변수에 할당될 수 없습니다.
- ts는 길이도 체크함.
const triple: [number,number,number] = [1,2,3]; const double: [number,number] = triple; // ~~~~ [nunber,number,number] 형식은 // [nunber,number] 형식에 할당할 수 없습니다. // 'length'속성의 형식이 호환되지 않습니다. // 3 형식은 2 형식에 할당할 수 없습니다.
item 8
typeof
와 타입스크립트 타입은 다름. js 타입이 더 단순함.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
function email( {person, subject, body}: {person: Person, subject: string, body: string} ) { // ... }
item 9
const a : Person
(선언),as const
(단언)const a = <Person>{}
→ 이건 리액트 컴포넌트로 인식되어 잘 쓰지 않음.
const people: Person[] = ['alice', 'bob', 'jan'].map( (name): Person => ({name}) );
- DOM 엘리먼트 - ts는 dom에 접근할 수 없기 때문. ex) querySelector
- ! - 접두사로 쓰이면 boolean의 부정문, 접미사로 쓰이면 null이 아니라는 단언문으로 쓰임.
// !도 단언문임. 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
item10
- string의 경우 기본형이지만 메서드를 가지는 객체 타입으로 정의되어 있기도 함.
String.prototype
- 예를 들어 기본형 string을 String 객체로 wrapping하고, 메서드를 호출하고, 마지막에 wrapping한 객체를 버림 (몽키 패치).
- 몽키 패치란 런타임에 프로그램의 어떤 기능을 수정해서 사용하는 기법을 말함.
- 다른 기본형에도 동일하게 객체 래퍼 타입이 존재함. null, undifined 제외.
- 특징
// String은 자기 자신이 유일한 존재임. > "hello" === new String("hello") false > new String("hello") === new String("hello") false // 기본형에 할당하면 기존 속성이 사라짐. > x = "hello" > x.language = 'English' 'English' > x.language undefined
- 타입스크립트가 제공하는 타입 선언은 전부 기본형 타입으로 되어 있어 타입 정의 시 기본형을 사용하면 됨.
- new 없이 BigInt를 쓰면 기본형을 생성하지만 그래도 헷갈리니 기본형 쓰자.
item11
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' };
const obj = { numDoors: 1, ceilingHeightFt: 10, elephant: 'present', }; const r: Room = obj; // OK
- 잉여 속성 체크가 할당 가능 검사와는 별도의 과정이라는 것을 알아야 타입스크립트 타입 시스템에 대한 개념을 정확히 잡을 수 있음.
- 객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여 속성 체크가 수행됨.
- 객체 리터럴 - 잉여 속성 체크
- 객체 리터럴 x - 잉여 속성 체크 x
- 임시 변수를 도입하면 잉여 속성 체크를 건너뛸 수 있음.
item12
- function보다 const를 사용해 함수를 정의하자.
- 장점 1) 불필요한 코드의 반복을 줄임.
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;
- 장점 2) 함수의 전체 타입을 정의하면 코드가 간결하고 안전해짐.
// 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; }
item13
type
vsinterface
- 공통점
- 잉여 속성 체크함.
- 인덱스 시그니처 사용 가능. ex) [key: string]: string
- 함수 타입 정의 가능.
- 제너릭 사용 가능.
- 인터페이스는 타입을, 타입은 인터페이스를 확장할 수 있음.
// IStateWithPop과 TStateWithPop은 동일함. interface IStateWithPop extends TState { population: number; } type TStateWithPop = IState & { population: number; };
interface | type |
유니온 x | 유니온 o ex) type a = A | B |
튜플 표현에 어려움 (하단 예시 참조) | 튜플 표현이 쉬움 |
보강(선언 병합) 가능 | 보강 불가능 |
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
item14
- 타입도 반복을 줄여야 함. extends로 줄일 수 있음.
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'>;
- keyof를 사용해서 반복 줄이기
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"
- typeof를 사용해서 반복 줄이기
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
item15
- 동적 데이터에 인덱스 시그니처를 사용하자.
- csv 파일과 같은 형식을 인풋으로 받아 parsing 하는 함수가 있다고 할 때.
- 런타임 때까지의 객체 속성을 알 수 없을 경우.
- 인덱스 시그니처는 객체의 키, 밸류를 다음과 같이 표현한 것.
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; // }
item16
- 배열은 객체이므로 키는 숫자가 아닌 문자열임(js의 특성 때문).
- Object.keys로 숫자로 구성된 배열의 key 값을 뽑으면 string 타입의 숫자가 출력됨.
- 인덱스 시그니처로 사용된 number 타입은 버그를 잡기 위한 순수 타입스크립트 코드임.
- 만약 string으로 배열을 찾고 싶다면
arrayLike
를 사용하자.
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]; } }

item17
- 함수가 매개변수를 수정하지 않는다면 readonly로 선언하는 것이 좋음.
function arraySum(arr: readonly number[]){ let sum = 0, num; while((num = arr.pop()) !== undefined) { // 타입 에러 sum+=num; } return sum; }
- readonly 매개변수는 인터페이스를 명확하게 하며, 매개변수가 변경되는 것을 방지함.
- readonly는 얕게 동작하기에 깊은 readonly 타입을 사용하고 싶다면 ts-essetials에 있는 DeepReadonly 제네릭을 활용하자.
item18
- 리액트에는 props가 변경될 때 해당 컴포넌트와 자식 컴포넌트가 리렌더링되는데, 이벤트 핸들러 같이 눈에 보이는 요소가 아니라면 리렌더링할 필요가 없음.
interface ViewProps { // data xs: number[]; ys: number[]; // display xRange: [number,number]; yRange: [number, number]; color: string; // event onClick: (x:number, y: number, index: number) => void; }
- 보수적 접근법 또는 실패에 닫힌 접근법
- 모든 props에 대해 변경을 감지함.
function shouldUpdate( oldProps: ViewProps, newProps: ViewProps,){ let k: keyof ViewProps; for (k in oldProps){ if(oldProps[k] !== newProps[k]){ if(k !== "onClick") return true; } } }
- 실패에 열린 접근법으로 작성을 해보자.
- 빈번한 렌더링은 막지만 정작 필요할 때 뷰가 바뀌지 않을 수 있음.
function shouldUpdate( oldProps: ViewProps, newProps: ViewProps,){ return( oldProps.xs !== newProps.xs || oldProps.ys !== newProps.ys || oldProps.xRange !== newProps.xRange || oldProps.yRange !== newProps.yRange || oldProps.color !== newProps.color ) }
- 타입체커가 위와 같은 역할들을 대신하도록 만듦.
const REQUIRES_UPDATE: {[k in keyof ViewProps]: boolean} = { xs: true, ys: true, xRange: true, yRange: true, color: true, onClick: false } function shouldUpdate( oldProps: ViewProps, newProps: ViewProps,) { let k: keyof ViewProps; for (k in oldProps){ if(oldProps[k] !== newProps[k] && REQUIRES_UPDATE[k]){ return true; } } }
[k in keyof ViewProps]
는 타입 체커에게 ViewProps와 동일한 속성을 가져야 한다는 것을 알려줌.
REQUIRES_UPDATE
에 boolean 값을 가진 객체를 사용함. 나중에 추가 속성이 더해질때 REQUIRES_UPDATE에서 에러가 발생할 것임.