# 이펙티브 타입스크립트

(댄 밴더캄 지음, 장원호 옮김, 인사이트, 2021)

# 아이템1. 타입스크립트와 자바스크립트의 관계 이해하기

  • 명시적으로 states를 선언하여 의도를 분명하게 해야한다. 그래야 오류가 어디서 발생했는지 찾을 수 있고, 제시된 해결책도 올바르다.
  • 자바스크립트의 런타임 동작을 모델링하는 것은 타입스크립트 타입 시스템의 기본 원칙.
  • TS는 JS 런타임 동작을 모델링하는 타입 시스템을 가지고 있기 때문에 런타임 오류를 발생시키는 코드를 찾아내려고 합니다. 그러나 모든 오류를 찾아내리라 기대해선 안됨. 타입 체커를 통과하면서도 런타임 오류를 발생시키는 코드는 충분히 존재할 수 있음.

# 아이템2. 타입스크립트의 설정 이해하기

  • 가급적 설정 파일을 사용. 그래야 TS를 어떻게 사용할 계획인지 동료들과 다른 도구들이 알 수 있음. 설정 파일은 ts --init 명령어로 생성.
  • 커맨드 라인보다 tsconfig.json을 사용하자.
  • TS는 타입 정보를 가질 때 가장 효과적이기에 가급적 noImplicitAny를 설정해야 한다.
  • "undefined는 객체가 아닙니다." 같은 런타임 오류를 방지하기 위해 NullChecks를 설정하는 것이 좋다.
  • TS에 엄격한 체크를 원한다면 strict 설정을 고려해봐야 한다.

# 아이템3. 코드의 생성과 타입이 관계없음을 이해하기

큰 틀에서 보면 ts 컴파일러는 두 가지 역할을 수행.

  • 최신 ts/js를 브라우저에서 동작할 수 있도록 구버전의 js로 트랜스파일
  • 코드의 타입 오류를 체크
    위 두 가지는 완벽히 독립적이라는 것. ts -> js로 변환될 때 코드 내의 타입에는 영향을 주지 않음.
  • 즉, 타입 오류가 있는 코드도 컴파일이 가능. ts에 오류는 c나 java같은 언어의 경고(warning)와 비슷함. 문제가 될 만한 부분을 알려 주지만 그렇다고 빌드를 멈추지는 않음.

# 요약

  • 런타임에는 타입 체크가 불가능함
  • 타입 연산은 런타임에 영향을 주지 않음.
  • 런타임 타입은 선언된 타입과 다를 수 있음.
  • 타입스크립트 타입으로는 함수를 오버로드할 수 없다.
  • 타입스크립트 타입은 런타임 성능에 영향을 주지 않는다.

# 아이템4. 구조적 타이핑에 익숙해지기

  • 자바스크립트가 덕 타이핑 기반이고 타입스크립트가 이를 모델링하기 위해 구조적 타이핑을 사용함을 이해해야 합니다. 어떤 인터페이스에 할당 가능한 값이라면 타입 선언에 명시적으로 나열된 속성을 가지고 있을 것.
  • 클래스 역시 구조적 타이핑 규칙을 따름. 클래스의 인스턴스가 예상과 다를 수 있다.
  • 구조적 타이핑을 이용하면 테스팅을 수월하게 할 수 있음.

# 아이템5. any 타입 지양하기

TS는 코드에 타입을 조금씩 추가할 수 있기 때문에 점진적이며, 언제든지 타입체커를 해제할 수 있기 때문에 선택적입니다. 타입체커가 찾아낸 오류는 as any를 추가해 해결 가능함. 그러나 특별한 경우를 제외하고 사용 x.

  • any 타입에는 타입 안정성이 없다.

  • any는 함수 시그니처(contract)를 무시한다.

  • any 타입에는 언어 서비스가 적용되지 않음. Rename Symbol같은 이름 변경 서비스 이용불가.

  • any 타입은 코드 리팩터링 때 버그를 감춘다.

    요약: any 타입은 타입체커와 타입스크립트 언어 서비스를 무력화시켜 버린다. 따라서 진짜 문제점을 감추며, 개발경험을 나쁘게 하고, 타입 시스템의 신뢰도를 떨어뜨립니다. 최대한 사용을 피하도록.

# 2장

ts에 가장 중요한 역할은 타입시스템. 어떻게 사용하고, 무엇을 결정해야하며, 가급적 사용하지 말아야 할 기능 중점적으로.

# 아이템6. 편집기를 사용하여 타입 시스템 탐색하기

  • tsc: 타입스크립트 컴파일러
  • tsserver: 단독으로 실행 가능한 타입스크립트 서버 언어서비스(코드 자동완성, 명세 specification, 검사, 검색, 리팩터링)
  • 편집기에서 타입스크립트 언어 서비스를 적극 활용하여, 타입 시스템이 어떻게 동작하는지 어떻게 타입을 추론하는지 개념을 잡자.
  • 동작을 어떻게 모델링하는지 알기 위해 타입 선언 파일을 찾아보는 방법을 터득해야함.

# 아이템7. 타입이 값들의 집합이라고 생각하기

never < unit type,literal < union

  • 집합의 관점에서 타입체커의 주요기능은 하나의 집합이 다른 집합의 부분 집합인지 검사하는 것.
  • 타입을 값의 집합으로 생각하면 이해하기 쉬움. 이 집합은 유한(boolean or literal type)하거나 무한(number or string)한다.
  • 타입스크립트 타입은 엄격한 상속 관계가 아니라 겹쳐지는 집합으로 표현됨. 두 타입은 서로 서브타입이 아니면서도 겹쳐질 수 있다.
  • 한 객체의 추가적인 속성이 타입 선언에 언급되지 않더라도 그 타입에 속할 수 있다.
  • 타입의 연산은 집합의 범위에 적용됩니다. A와 B의 인터섹션(교집합)은 A의 범위와 B의 범위의 인터섹션입니다. 객체 타입에서는 A&B인 값이 A와 B의 속성을 모두 가짐을 의미.

# 아이템8. 타입 공간과 값 공간의 심벌 구분하기

  • 타입 선언(😃 또는 단언문(as) 다음에 나오는 심벌은 타입인 반면, = 다음에 나오는 모든 것은 값이다.
  • 클래스가 타입으로 쓰일 때는 형태(속성과 메서드)가 사용되는 반면, 값으로 쓰일 때는 생성자가 사용.
  • 타입의 속성을 얻을 때에는 반드시 obj['field']를 사용해야 함.
  • 값에서 &와 |는 AND와 OR 비트연산.타입에서는 인터섹션과 유니온.
  • const는 새 변수를 선언하지만, as const는 리터럴 또는 리터럴 표현식의 추론 타입을 바꿉니다.

# 요약

  • 타입스크립트 코드를 읽을 때 타입인지 값인지 구분.
  • 모든 값은 타입을 갖지만, 타입은 값을 가지지 않음. type, interface 같은 키워드는 타입 공간에서만 존재
  • class나 enum같은 키워드는 타입과 값 두 가지로 사용가능.
  • "foo"는 문자열 리터럴이거나, 문자열 리터럴 타입일 수 있다.
  • typeof, this 그리고 많은 다른 연산자들과 키워드들은 타입공간, 값공간에서 다른 목적으로 사용될 수 있다.

# 아이템9. 타입 단언보다는 타입 선언을 사용하기

  • 타입 단언은 강제로 타입을 지정했으니 타입 체커에게 오류를 무시하라고 하는 것임. 속성을 추가할 때도.
  • 타입 단언(as Type)이 꼭 필요한 경우가 아니라면, 안정성 체크도 되는 타입 선언(: Type)을 사용.
const people: Person[] = ['alice', 'bob', 'jan'].map(
    (name): Person => ({name})
); // 타입은 Person[]
  • 타입 단언은 타입 체커가 추론한 타입보다 내가 판단하는 타입이 더 정확할 때 사용. 예) DOM 엘리먼트 등..
  • 화살표 함수의 반환 타입을 명시하는 방법을 터득
  • 타입스크립트보다 타입 정보를 더 잘 알고 있는 상황이라면 타입 단언문과 null 아님 단언문을 사용.

# 아이템10. 객체 래퍼 타입 피하기

  • 기본형 값에 메서드를 제공하기 위해 객체 래퍼 타입이 어떻게 쓰이는지 알아야함. 직접 사용이나 인스턴스 생성은 피하자.
  • 객체 래퍼 타입은 지양. 대신 기본형 타입을 사용해야 함. 예) String 대신 string, Number 대신 number.

# 아이템11. 잉여 속성 체크의 한계 인지하기

  • 객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여 속성 체크가 수행됨.
  • 잉여 속성 체크는 오류를 찾는 효과적인 방법이지만, ts 타입 체커가 수행하는 일반적인 구조적 할당 가능성 체크와 역할이 다름.
  • 잉여 속성 체크에는 한계가 있다. 임시 변수를 도입하면 잉여 속성 체크를 건너뛸 수 있다.

# 아이템12. 함수 표현식에 타입 적용하기

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;
}
  • 매개변수나 반환 값에 타입을 명시하기보다는 함수 표현식 전체에 타입 구문을 적용하는 것이 좋다.
  • 만약 같은 타입 시그니처를 반복적으로 작성한 코드가 있다면 함수 타입을 분리해 내거나 이미 존재하는 타입을 찾아보기. 라이브러리를 직접 만든다면 공통 콜백에 타입을 제공해야 한다.
  • 다른 함수의 시그니처를 참조하려면 typeof fn을 사용.

# 아이템 13. 타입과 인터페이스의 차이점 알기

  • 인터페이스는 유니온 타입같은 복잡한 타입을 확장하지는 못함. 복잡한 타입을 확장하고 싶다면 타입과 &을 사용해야함.
  • 클래스 구현(implements)할 때는, 타입과 인터페이스 둘 다 사용가능.
// 듀플과 배열 타입은 type 키워드로 보다 간결하게 표현 가능.
type Pair = [number, number];
type StringList = string[];
type NameNums = [string, ...number[]];
// 인터페이스는 타입에는 없는 몇 가지 기능 존재. 보강(augment)이 가능. 선언 병합(declaration merging)
interface State {
    name: string;
    capital: string;
}

interface State {
    population: number;
}

const wyoming: State = {
    name: 'Wyoming',
    capital: 'Cheyenne',
    population: 500_000
}; //정상
  • 복잡한 타입이라면 고민할 필요없이 type 사용. 하지만 간단한 객체 타입이라면 일관성과 보강의 관점에서 interface 고려도 필요.
  • 즉, 프로젝트 스타일에 맞춰 사용.
  • 스타일이 확립되지 않은 프로젝트라면 어떤 API에 대한 타입 선언을 작성해야한다면 interface를 사용하는 것이 좋음. API가 변경될 때 사용자가 인터페이스를 통해 새로운 필드를 병합할 수 있어 유용.
  • 그러나 내부적으로 사용되는 타입에 선언 병합이 발생하는 것은 잘못된 설계. 따라서 이럴땐 type을 사용.

# 아이템 14. 타입 연산과 제네릭 사용으로 반복 줄이기

type TopNavState = Pick<State, 'userId' | 'pageTitle' | 'recentFiles'>; 
interface Options {
    width: number;
    height: number;
    color: number;
    label: number;
}

interface OptionsUpdate {
    width?: number;
    height?: number;
    color?: number;
    label?: number;
}

type OptionsUpdate = { [k in keyof Options]?: Options[k] };
  • DRY 원칙 (don't repeat yourself)
  • 타입에 이름을 붙여 반복을 피해야함. extends를 사용해서 인터페이스 필드의 반복을 피함.
  • 타입들 간의 매핑을 위한 keyof, typeof, 인덱싱 등 알아둘것
  • 제네릭 타입은 타입을 위한 함수와 같다. 타입을 반복하는 대신 제네릭 타입을 사용하여 타입들간의 매핑을 하는 것이 좋다.
  • Pick, Partial, ReturnType 같은 제네릭 타입에 익숙해져야함.

# 아이템 15. 동적 데이터에 인덱스 시그니처 사용하기

type Rocket = { [property: string]: string };
const rocket: Rocket = {
    name: 'Falcon 9',
    variant: 'v1.0',
    thrust: '4,940kN',
}; // 정상

[property: string]: string 가 인덱스 시그니처. 다음과 같은 세 가지 의미

  • 키의 이름: 키의 위치만 표시하는 용도. 타입체커에서는 사용x
  • 키의 타입: string, number, symbol의 조합이어야하지만 보통은 string 사용.
  • 값의 타입: 어떤 것이든 가능. 단점
  • 잘못된 키를 포함한 모든 키를 허용. name 대신 Name이 들어와도 유효.
  • 특정 키가 필요하지 않음. {}도 유효.
  • 키마다 다른 타입을 갖지 못함.

# 요약

  • 런타입 때까지 객체의 속성을 알 수 없을 경우에만 인덱스 시그니처를 사용.
  • 안전한 접근을 위해 인덱스 시그니처의 값 타입에 undefined를 추가하는 것을 고려.
  • 가능하면 인터페이스, Record, 매핑된 타입 같은 인덱스 시그니처보다 정확한 타입을 사용 권장.

# 아이템 16. number 인덱스 시그니처보다는 Array, 듀플, ArrayLike를 사용하기.

  • 배열은 객체이므로 키는 숫자가 아니라 문자열. 인덱스 시그니처로 사용된 number 타입은 버그를 잡기 위한 순수 자바스크립트 코드.
  • 인덱스 시그니처에 number를 사용하기보다 Array나 튜플, 또는 ArrayLike 타입을 사용하는 것이 좋음.

# 아이템 17. 변경 관련된 오류 방지를 위해 readonly 사용하기

  • 만약 함수가 매개변수를 수정하지 않는다면 readonly로 선언하는 것이 좋다. readonly는 매개변수를 명확하게 하며, 매개변수가 변경되는 것을 방지함.
  • readonly를 사용하면 변경하면서 발생하는 오류를 방지할 수 있고, 변경이 발생하는 코드도 쉽게 찾을 수 있다.
  • const 와 readonly의 차이를 이해.
  • readonly는 얕게 동작함.

# 아이템 18. 매핑된 타입을 사용하여 값을 동기화하기

  • 매핑된 타입을 사용해서 관련된 값과 타입을 동기화하도록 함.
  • 인터페이스에 새로운 속성을 추가할 때, 선택을 강제하도록 매핑된 타입을 고려해야함.

# 3장

# 아이템 19. 추론 가능한 타입을 사용해 장황한 코드 방지하기

  • ts가 타입을 추론할 수 있다면 타입 구문을 작성하지 않는 게 좋음.
  • 이상적인 경우 함수/메서드의 시그니처에는 타입 구문이 있지만, 함수 내의 지역 변수에는 타입 구문이 없음.
  • 추론될 수 있는 경우라도 객체 리터럴과 함수 반환에는 타입 명시를 고려. 이는 내부 구현의 오류가 사용자 코드 위치에 나타나는 것을 방지.

# 아이템 20. 다른 타입에는 다른 변수 사용하기

다른 타입에 별도의 변수를 사용하는 게 바람직한 이유

  • 서로 관련이 없는 두 개의 값을 분리
  • 변수명을 더 구체적으로 지을 수 있음.
  • 타입 추론을 향상. -> 타입 구문 불필요함
  • 타입이 간결해짐.(string | number 대신 string 과 number 사용)
  • let 대신 const 변수를 선언하게 됩니다. const 변수를 선언하면 코드가 간결해지고 타입 체커가 타입을 추론하기도 수월해짐.

요약

  • 변수의 값은 바뀔 수 있지만 타입의 값은 일반적으로 바뀌지 않는다.
  • 혼란을 막기위해 타입을 다른 값으로 다룰 때에는 변수를 재사용하지 않도록.

# 아이템 21. 타입 넓히기

  • 객체의 경우 타입스크립트 넓히기 알고리즘은 각 요소를 let으로 할당된 것처럼 다룹니다.
  • 값 뒤에 as const를 작성하면 타입스크립트는 최대한 좁은 타입으로 추론함.

# 아이템 22. 타입 좁히기

// 자바스크립트에서 typeof null이 "object"이기 때문에 if 구문에서 null이 제외되지 않음
const el = document.getElementsById('foo');
if (typeof null === 'object') {
    el;
}
// filter 함수를 사용해서 undefined를 걸러 내려고 해도 잘 동자기하지 않음.
const members = ['janet', 'michael'].map(
    who => jackson5.find(n => n === who)
).filter(who => who !== undefined); // 타입이 (string | undefined)[]
// 이럴 때 타입 가드를 사용하면 타입을 좁힐 수 있다.
function isDefined<T>(x: T | undefined): x is T {
    return x !== undefined;
}

const members = ['janet', 'michael'].map(
    who => jackson5.find(n => n === who)
).filter(isDefined); // 타입이 string[]
  • 태그된/구별된 유니온과 사용자 정의 타입 가드를 사용하여 타입 좁히기 과정을 원활하게 만들 수 있습니다.

# 아이템 23. 한꺼번에 객체 생성하기

const pt = {};
pt.x = 3;
// ~ '{}' 형식에 'x'속성이 없습니다.
pt.y = 4;
// ~ '{}' 형식에 'y'속성이 없습니다.
interface Point {
    x: number;
    y: number;
}

const pt: Point = {};
// ~ '{}' 형식에 'Point'형식의 x, y속성이 없습니다.
pt.x = 3;
pt.y = 4;
// 1. 객체를 한번에 정의하는 방법으로 해결
const pt = {
    x: 3,
    y: 4,
}; // 정상

// 2. 타입 단언문 as를 사용해 타입체커 통과하는 방법
const pt = {} as Point;
pt.x = 3;
pt.y = 4; //정상

// 3.
const pt: Point = {
    x: 3,
    y: 4,
}; // 추천

가끔 객체나 배열을 반환해서 새로운 객체나 배열을 생성하고 싶을 때가 있다. 이런 경우 루프 대신 내장된 함수형 기법 또는 로대시(lodash)같은 유틸리티 라이브러리를 사용하는 것이 '한꺼번에 객체 생성하기' 관점에서 보면 옳다.

  • 속성을 제각각 추가하지 말고 한꺼번에 객체로 만들어야 함. 안전한 타입으로 속성을 추가하려면 객체전객({...a, ...b})를 사용.
  • 객체에 조건부로 속성을 추가하는 방법을 익히기.

# 아이템 24. 일관성 있는 별칭 사용하기 객체 비구조화를 이용할 때 주의점

  • 전체 속성이 아닌 선택적 속성일 경우 속성체크가 더 필요. 따라서 타입의 경계의 null값을 추가하는 것이 좋음
  • 빈 배열은 '없음'을 나타내는 좋은 방법

# 요약

  • 별칭은 타입스크립트가 타입을 좁히는 것을 방해. 따라서 변수에 별칭을 사용할 때는 일관되게 사용.
  • 비구조화 문법을 사용하여 일관된 이름을 적극 사용.
  • 함수 호출이 객체 속성의 타입 정제를 무효화할 수 있다는 점 주의. 속성보다 지역 변수를 사용하면 타입 정제를 믿을 수 있음.

# 아이템 25. 비동기 코드에는 콜백 대신 async 함수 사용하기

콜백 보단 프로미스나 async/await 사용 추천 이유

  • 콜백보다는 프로미스가 코드를 작성하기 쉬움.
  • 콜백보다는 프로미스가 타입을 추론하기 쉬움.

프로미스보다 async/await 사용해야하는 이유

  • 일반적으로 더 간결. 직관적인 코드가 됨.
  • async 함수는 항상 프로미스를 반환하도록 강제됨.

요약

  • 어떤 함수가 프로미스를 반환한다면 async로 선언하는 것이 좋음.

# 아이템 26. 타입 추론에 문맥이 어떻게 사용되는지 이해하기

타입스크립트는 할당 시점에 타입을 추론합니다. 때문에, 타입 선언에서 가능한 값을 제한. 상수로 만드는 것을 고려.

  • 타입 추론에서 문맥이 어떻게 쓰이는지 주의해서 살펴봐야함.
  • 변수를 뽑아서 별도로 선언했을 때 오류가 발생하면 타입 선언을 추가.
  • 변수가 정말로 상수라면 상수 단언(as const)을 사용. 그러나 상수 단언을 사용하면 정의한 곳이 아닌 사용한 곳에서 오류가 발생하니 주의.

# 아이템 27. 함수형 기법과 라이브러리로 타입 흐름 유지하기

  • 타입 흐름을 개선하고, 가독성을 높이고, 명시적인 타입 구문의 필요성을 줄이기 위해 직접 구현하기보다는 내장된 함수형 기법과 로대시(lodash)같은 유틸리티 라이브러리를 사용하는 것을 권장

# 아이템 28. 유효한 상태만 표현하는 타입을 지향하기

  • 유효한 상태와 무효한 상태를 둘 다 표현하는 타입은 혼란을 초래하기 쉽고 오류를 유발하게 됨.
  • 유효한 상태만 표현하는 타입을 지향. 코드가 길어지거나 표현하기 어렵지만 결국은 시간 절약.

# 아이템 29. 사용할때는 너그럽게, 생성할 때는 엄격하게

  • 함수의 매개변수는 타입의 범위가 넓어도 되지만, 결과를 반환할 때는 일반적으로 타입의 범위가 더 구체적이어야 한다.
  • 선택적 속성과 유니온 타입은 반환 타입에서 잘 사용을 하지 않고 보통 매개변수 타입에 사용.
  • 매개변수와 반환 타입의 재사용을 위해서 기본 형태(반환 타입)와 느슨한 형태(매개변수 타입)을 도입하는 것이 좋습니다.

# 아이템 30. 문서에 타입 정보를 쓰지 않기

  • 함수의 입력과 출력의 타입을 코드로 표현하는 것이 주석으로 표현하는 것보다 더 나은 방법.
  • 값 또는 매개변수를 변경하지 않는다고 설명하는 주석은 좋지 않음. 그 대신 readonly로 선언하여 타입스크립트가 규칙을 강제할 수 있도록.
  • 주석과 변수명에 타입 정보를 적는 것은 피해야함. 최악의 경우 타입 정보 모순 발생.
  • 타입이 명확하지 않은 경우 변수명에 단위 정보를 포함하는 것을 고려. (예 timeMs, temperatureC)

# 아이템 31. 타입 주변에 null 값 배치하기

  • 한 값의 null 여부가 다른 값의 null 여부에 암시적으로 관련되도록 설계해선 안된다.
  • API 작성 시에는 반환 타입을 큰 객체로 만들고 반환 타입 전체가 null이거나 null이 아니게 만들어야 함.
  • 클래스를 만들 때는 필요한 모든 값이 준비되었을 때 생성하여 null이 존재하지 않도록 하는 것이 좋습니다.
  • strickNullCheck를 설정하면 코드에 많은 오류가 표시되겠지만, null 값과 관련된 문제점을 찾아낼 수 있기 때문에 반드시 필요함.

# 아이템 32. 유니온의 인터페이스 보다는 인터페이스의 유니온을 사용하기

  • 유니온의 인터페이스보다 인터페이스의 유니온이 더 정확하고 타입 스크립트가 이해하기도 좋습니다.
  • 타입스크립트가 제어 흐름을 분석할 수 있도록 타입에 태그를 넣는 것을 고려해야 함. 태그된 유니온은 타입스크립트와 매우 잘 맞기 때문에 자주 볼 수 있는 패턴입니다.

# 아이템 33. string 타입보다 더 구체적인 타입 사용하기

  • 문자열을 남발하여 선언된 코드를 피하자. 모든 문자열을 할당할 수 있는 string 타입보다는 더 구체적인 타입을 사용하는 것이 좋습니다.
  • 변수의 범위를 보다 정확하게 표현하고 싶다면 string 타입보다는 문자열 리터럴 타입의 유니온을 사용하면됨. 타입 체크를 보다 더 엄격히 하고 생산성을 향상 시킬 수 있다.
  • 객체의 속성 이름을 함수 매개변수로 받을 때는 string 보다 keyof T를 사용하는 것이 좋습니다.

# 아이템 34. 부정확한 타입보다는 미완성 타입을 사용하기

  • 타입 안정성에서 불쾌한 골짜기는 피해야 합니다. 타입이 없는 것보다 잘못된 게 더 나쁨.
  • 정확하게 타입을 모델링 할 수 없다면, 부정확하게 모델링하지 말아야 합니다. 또한 any와 unknown을 구별해서 사용해야 함.
  • 타입 정보를 구체적으로 만들수록 오류 메시지와 자동 완성 기능에 주의를 기울여야 합니다. 정확도뿐만 아니라 개발 경험과도 관련됨.

# 아이템 35. 데이터가 아닌, API와 명세를 보고 타입을 만들기

  • 타입 안정성을 얻기 위해 API 또는 데이터 형식에 대한 타입 생성을 고려해야 합니다.
  • 데이터에 드러나지 않는 예외적인 경우들이 문제가 될 수 있기 때문에 데이터보다는 명세로부터 코드를 생성하는 것이 좋음.

# 아이템 36. 해당 분야의 용어로 타입 이름 짓기

  • 이름 짓기 역시 타입 설계에 있어서 중요한 부분. 엄선된 타입, 속성, 변수의 이름은 의도를 명확하기 하고 코드와 타입의 추상화 수준을 높여줌.
interface Animal {
    name: string;
    endangered: boolean;
    habitat: string;
}

const leopard: Animal = {
    name: 'Snow Leopard',
    endangered: false,
    habitat: 'tundra',
};

interface Animal {
    commonName: string;
    genus: string;
    species: string;
    status: ConservationStatus;
    climates: KoppenClimate[];
}

type ConservationStatus = 'EX' | 'EW' | 'CR' | 'EN' | 'VU' | 'NT' | 'LC';
type KoppenClimate = |
    'Af' | 'Am' | 'As' | 'Aw' |
    'BSh' | 'BSk' | 'BWh' | 'BWk' |
    'Cfa' | 'Cfb' | 'Cfc' | 'Csa' | 'Csb' | 'Csc' | 'Cwa' | 'Cwb' | 'Cwc' |
    'Dfa' | 'Dfb' | 'Dfc' | 'Dfd' |
    'Dsa' | 'Dsb' | 'Dsc' | 'Dwa' | 'Dwb' | 'Dwc' | 'Dwd' |
    'EF' | 'ET';
const snowLeopard: Animal = {
    commonName: 'Snow Leopard',
    genus: 'Panthera',
    species: 'Uncia',
    status: 'VU',  // vulnerable
    climates: ['ET', 'EF', 'Dfd'],  // alpine or subalpine
};

세 가지를 개선

  • name은 commonName, genus, species 등 더 구체적인 용어로 대체
  • endangered는 동물 보호 등급에 대한 IUNC 표준 분류체계로
  • habitat은 기후를 뜻하는 climates로 변경되었으면, 쾨펜 기후 분류를 사용.

코드를 표현하고자 하는 모든 분야에는 주제를 설명하기 위한 전문 용어들이 있습니다. 자체적으로 용어를 만들어 내려하지 말고, 해당 분양에서 이미 존재하는 용어를 사용해야 합니다. 이러한 용어사용이 사용자와 소통에 유리하며 타입의 명확성을 올린다.

타입, 속성, 변수에 이름을 붙일 때 명심해야할 세 가지 규칙이 있습니다.

  • 동일한 의미를 나타낼 때는 같은 용어를 사용해야 합니다. 글이나 말할때 처럼 동의어를 사용하는 것은 좋지 않음.
  • data, info, thing, item, object, entity 같은 모호하고 의미 없는 이름은 피해야합니다.
  • 이름을 지을 때는 포함된 내용이나 계산방식이 아니라 데이터 자체가 무엇인지 고려해야합니다. 에를 들어 INodeList보다 Directory가 더 의미 있는 이름입니다. Directory는 구현의 측면이 아니라 개념적인 측면에서 디렉터리를 생각하게 합니다.
  • 좋은 이름은 추상화의 수준을 높여주고 의도치 않은 충돌의 위험성을 줄여 줍니다.

# 아이템 37. 공식 명칭에는 상표를 붙이기

  • 타입스크립트는 구조적 타이핑(덕 타이핑)을 사용하기 깨문에, 값을 세밀하게 구분하지 못하는 경우가 있습니다. 값을 구분하기 위해 공식 명칭이 필요하다면 상표를 붙이는 것을 고려해야함.
  • 상표 기법은 타입 시스템에서 동작하지만 런타임에 상표를 검사하는 것과 동일한 효과를 얻을 수 있음.

# 5장. any 다루기

# 아이템 38. any 타입은 가능한 좁은 범위에서만 사용하기

  • 의도치 않은 타입 안정성의 손실을 피하기 위해 any의 사용 범위를 최소한으로 좁혀야 함.
  • 함수의 반환 타입이 any인 경우 타입 안정성이 나빠집니다. 따라서 any 타입을 반환하면 절대 안 됨.
  • 강제로 타입 오류를 제거하려면 any 대신 @is-ignore를 사용하는 것이 좋음,

# 아이템 39. any를 구체적으로 변형해서 사용하기

  • any를 사용할 때는 정말로 모든 값이 허용되어야만 하는지 면밀히 검토해야 함.
  • any보다 정확하게 모델링 할 수 있도록 any[] 또는 {[id: string]: any} 또는 () => any 처럼 구체적인 형태를 사용.

# 아이템 40. 함수 안으로 타입 단언문 감추기

  • 타입 선언문은 일반적으로 타입을 위험하게 만들지만 상황에 따라 필요하기도 하고 현실적인 해결책이 되기도 함. 불가피하게 사용해야 한다면 정확한 정의를 가지는 함수안으로 숨기도록 하자.

# 아이템 41. any의 진화를 이해하기

  • 일반적인 타입들은 정제되기만 하는 반면, 암시적 any와 any[] 타입은 진화할 수 있다. 이러한 동작이 발생하는 코드를 인지하고 이해할 수 있어야 함.
  • any를 진화시키는 방식보다 명시적 타입 구문을 사용하는 것이 안전한 타입을 유지하는 방법.

# 아이템 42. 모르는 타입의 값에는 any 대신 unknown을 사용하기

  • unknown은 any 대신 사용할 수 있는 안전한 타입입니다. 어떠한 값이 있지만 그 타입을 알지 못하는 경우라면 unknown을 사용.
  • 사용자가 타입 단언문이나 타입 체크를 사용하도록 강제하라면 unknown을 사용하면 됨.
  • {}, object, unknown의 차이점 숙지

# 아이템 43. 몽키 패치보다는 안전한 타입을 사용하기

# 아이템 44. 타입 커버리지를 추적하여 타입 안정성을 유지하기

  • noImplicitAny가 설정되어 있어도, 명시적 any 또는 서드파티 타입 선언(@type)을 통해 any 타입은 코드 내에 여전히 존재할 수 있다는 점을 주의.

# 아이템 45. devDependencies에 typescript와 @types 추가하기

  • 타입스크립트를 시스템 레벨로 설치하면 안 됩니다. 타입스크립트를 프로젝트의 devDependencies에 포함시키고 팀원 모두가 동일한 버전을 사용하도록 해야함.
  • @type 의존성은 dependencies가 아니라 devDependencies에 포함시켜야 함. 런타임에 @type이 필요한 경우라면 별도의 작업 필요할 수 있음.

# 아이템 46. 타입 선언과 관련된 세 가지 버전 이해하기

# 아이템 47. 공개 API에 등장하는 모든 타입을 익스포트하기

# 아이템 48. API 주석에 TSDOC 사용하기

  • 익스포트된 함수, 클래스, 타입에 주석을 달 때는 JSDoc/TSDoc 형태 사용. 형태의 주석 정보를 표시해 줍니다.
  • JSDoc에는 타입 정보를 명시하는 규칙이 있지만, 타입스크립트에서는 타입 정보가 코드에 있기 때문에 TSDoc에서는 타입 정보를 명시하면 안됨.

# 아이템 49. 콜백에서 this에 대한 타입 제공하기

  • 콜백 함수에서 this를 사용해야 한다면, 타입 정보를 명시

# 아이템 50. 오버로딩 타입보다는 조건부 타입을 사용하기

  • 조건부 타입은 추가적인 오버로딩 없이 유니온 타입을 지원할 수 있음.

# 아이템 51. 의존성 분리를 위해 미러 타입 사용하기

# 아이템 52. 테스팅 타입의 함정에 주의하기

  • 타입 관련된 테스트에서 any를 주의해야 합니다. 더 엄격한 테스트를 위해 dtslint같은 도구를 사용하는 것이 좋습니다.

# 7장. 코드를 작성하고 실행하기

# 아이템 53. 타입스크립트 기능보다는 ECMAScript기능을 사용하기

  • 일반적으로 타입스크립트 코드에서 모든 타입 정보를 제거하면 자바스크립트가 되지만, 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터는 타입 정보를 제거한다고 자바스크립트가 되진 않는다.
  • 타입스크립트의 역할을 명확하게 하려면, 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터는 사용하지 않는 것이 좋음.

# 아이템 54. 객체를 순회하는 노하우

  • 객체를 순회할 때, 키가 어떤 타입인지 정확히 파악하고 있다면 let k: keyof T와 for-in 루프를 사용합니다. 함수의 매개변수로 쓰이는 객체에는 추가적인 키가 존재할 수 있다는 점을 명심.
  • 객체를 순회하며 키와 값을 얻는 가장 일반적인 방법은 Object.entries를 사용하는 것.
function foo(abc: ABC) {
    for (const [k, v] of Object.entries(abc)) {
        k // string 타입
        v // any 타입
    }
}

# 아이템 55. DOM의 계층 구조 이해하기

# 아이템 56. 정보를 감추는 목적으로 private 사용하지 않기

  • public, protected, private 접근 제어자는 타입 시스템에서만 강제될 뿐. 런타임에서는 소용이 없으며 단언문을 통해 우회할 수 있습니다. 때문에 접근 제어자로 감추려고 해선 안됨.
  • 확실히 데이터를 감추고 싶다면 클로저를 사용해야 함.

# 아이템 57. 소스맵을 사용하여 타입스크립트 디버깅하기

  • 원본 코드가 아닌 변환된 자바스크립트로 디버깅 X. 소스맵을 사용하여 런타입에 타입스크립트를 디버깅 O.
  • 소스맵이 최종적으로 변환된 코드에 매핑되었는지 확인.
  • 소스맵에 원본 코드가 그대로 포함되도록 설정되어 있을 수도 있음. 공개되지 않도록 설정을 확인하자.

# 8장. 타입스크립트로 마이그레이션 하기

# 아이템 58. 모던 자바스크립트로 작성하기

  • ECMAScript 모듈 사용하기
  • 프로토타입 대신 클래스 사용하기
  • var 대신 let/const 사용하기
  • for(;;)대신 for-of 또는 배열 메서드 사용하기
  • 함수 표현식보다 화살표 함수 사용하기
  • 단축 객체 표현과 구조 분해 할당 사용하기
  • 함수 매개변수 기본값 사용하기
  • 저수준 프로미스나 콜백 대신 async/await 사용하기
  • 연관 배열에 객체 대신 Map과 Set 사용하기
  • 타입스크립트에 use strict 넣지 않기

# 아이템 59. 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기

# 아이템 60. allowJS로 타입스크립트와 자바스크립트 같이 사용하기

# 아이템 61. 의존성 관계에 따라 모듈 단위로 전환하기

# 아이템 62. 마이그레이션 완성을 위해 noImplicitAny 설정하기