woowacourse-study / 2022-thanks-giving-effective-typescript

🍂추석 연휴 집중🍂 이펙티브 타입스크립트를 읽는 모임 (✌️완주완료✌️)
6 stars 0 forks source link

2022.09.14 #11

Closed moonheekim0118 closed 1 year ago

moonheekim0118 commented 1 year ago

여기까지 오다니...너무 자랑스럽다 땡스기빙 타스

prefer2 commented 1 year ago

🎉완독!! 너무 자랑스럽다 땡스기빙 타스🎉

타입스크립트 기능보다는 ECMAScript 기능을 사용하기

타입스크립트 코드에서 모든 타입 정보를 제거하면 타입스크립트가 되지만, 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터는 타입 정보를 제거한다고 자바스크립트가 되지는 않는다.

열거형(enum)

const enum Flaver {
  VANILA = 0,
  CHOCOLATE = 1,
  STRAWBERRY = 2
}

let flavor = Flaver.CHOCOLATE; // js에서는 let flavor = 1
const enum Flaver {
  VANILA = 'vanila',
  CHOCOLATE = 'chocolate',
  STRAWBERRY = 'strawberry'
}

let flavor = Flaver.CHOCOLATE;
flavor = 'strawberry' //Type '"strawberry"' is not assignable to type 'Flaver'.

매개변수 속성

class Person{
  constructor(public name: string) {}
}

public name을 매개변수 속성이라 부른다.

클래스에 매개변수 속성만 존재한다면 클래스 대신 인터페이스로 만들고 객체 리터럴을 사용하는 것이 좋다.

객체를 순회하는 노하우

const obj = {
  one: 'uno',
  two: 'dos',
  three: 'tres'
};

for( const k in obj ){
  const v = obj[k]; // obj에 인덱스 시그니처가 없기 때문에 앨리먼트는 암시적으로 'any' 타입입니다
}

k와 obj 객체의 키 타입이 서로 다르게 추론된다.

let k: keyof typeof obj;
for(k in obj){
  const v = obj[k];
}

k의 타입을 구체적으로 명시해 주면 오류는 사라진다.

interface ABC {
  a: string;
  b: string;
  c: number;
}

function foo(abc: ABC) {
  for(const k in abc) {
    const v = abc[k]; // 'ABC'타입에 인덱스 시그니처가 없기 때문에 앨리먼트는 암시적으로 'any' 타입입니다
  }
}

const x = { a: 'a', b: 'b', c:2, d: new Date()}
foo(x) // 정상

foo 함수는 ABC 타입에 ‘할당 가능한' 어떠한 값이든 매개변수로 허용한다. 즉, ABC 타입에 할당 가능한 객체에는 a, b, c 외에 다른 속성이 존재할 수 있기 때문에, 타입스크립트는 ABC 타입의 키를 string 타입으로 선택한다.

단, keyof 키워드를 사용한 방법은 다른 문제점을 가지고 있다.

function foo(abc: ABC) {
  let k: keyof ABC;
  for(k in abc) { // let k: 'a' | 'b' | 'c'
    const v = abc[k]; // string | number 타입
  }
}

v가 string | number타입으로 한정이 되버린다. 추가적인 속성이 더해지는 경우(위의 예시처럼 d가 추가) 이는 어떠한 타입이든 될 수 있기 때문에 string | number 타입으로 추론된 것은 잘못이며 런타임 동작을 예상하기 어렵다.

단기 객체의 키와 값을 순회하고 싶다면 Object.entries를 사용하면 된다.

function foo(abc: ABC) {
  for( const [k, v] of Object.entries(abc)) {
    k // string
    v // any
  }
}

객체를 다룰 때에는 항상 프로토타입의 오염의 가능성을 염두해야 한다. for-in 구문을 사용하면, 객체의 정의에 없는 속성이 갑자기 등장할 수 있다.

for-in 루프에서 k가 string 키를 가지게 된다면 프로토타입 오염의 가능성을 의심해 봐야 한다.

keyof 선언은 상수이거나 추가적인 키 없이 정확한 타입을 원하는 경우 적적한다. Object.entries는 더욱 일반적으로 쓰이지만, 키와 값의 타입을 다루기 까다롭다.

💡 Object.entries는 타입스크립츠 3.8 기준으로 표준 함수가 이나며, tsconfig.jsondp es2017(ES8) 설정을 추가하여 사용할 수 있다.

정보를 감추는 목적으로 private 사용하지 않기

속성에 _를 붙이는 것은 단순히 비공개라고 표시한 것 뿐이다. 일반적인 속성과 동일하게 클래스 외부로 공개되어 있다.

public, protected, private 같은 접근 제어자는 타입스크립트 키워드이기 때문에 컴파일 후에는 제거된다. 이들은 단지 컴파일 시점에만 오류를 표시해 줄 뿐이며, 런타임에는 아무런 효력이 없다. 심지어 단언문을 사용하면 타입스크립트 상태에서도 private 속성에 접근할 수 있다. 즉, 정보를 감추기 위해 private을 사용하면 안된다.

class Diary {
  private secret = 'this is secret';
}

const diary = new Diary();
(diary as any).secret // 정상

자바스크립트에서 정보를 숨기기 위해 가장 효과적인 방법은 클로저를 사용하는 것이다. 그러나 클로저 방식은 동일 클래스의 개별 인스턴스 간의 속성 접근이 불가능하기 때문에 불편하다.

또 하나의 선택지로, 비공개 필드(#)을 사용할 수 있다. 비공개 필드 기능은 접두사로 #을 붙여서 타입 체크와 런타임 모두에서 비공개로 만드는 역할을 한다.

liswktjs commented 1 year ago

any 다루기

any 타입은 가능한 한 좁은 범위에서만 사용하기

any를 구체적으로 변형해서 사용하기

함수 안으로 타입 단언문 감추기

any의 진화를 이해하기

모르는 타입의 값에는 any 대신 unknown을 사용하기

function parseYAML(yaml:  string) : any { 

interface Book {
    name: string;
    author: string;
}

const book = parseYAML(`name: 'ssss' author: 'sssss'`); 
함수를 호출하는 곳에서 타입을 강제해 주지 않게 되면, book은 암시적으로 타입이 any가 되고 
타입 관련 오류가 발생할 확률들이 높아진다 
book.title => 오류 없다고 나오지만 , 런타임시 undefined에러가 발생하게 된다 

이럴 때에는, 함수의 반환 값에 unknown을 설정하게 되면 런타임 이전에 오류가 발생했다고 표시가 되게 된다

몽키 패치보다는 안전한 타입을 사용하기

타입 커버리지를 추적하여 타입 안전성 유지하기

타입 선언과 @types

devDependencies에 typescript와 @types 추가하기

⇒ 타입스크립트의 경우, 개발 도구 일 뿐이고 타입정보는 런타임에 존재하지 않기 때문에 기본적으로 devDepencies에 포함되어 있다

타입 선언과 관련된 세가지 버전 이해하기

공개 API에 등장하는 모든 타입을 익스포트 하기

API주석에 TSDoc 사용하기

콜백에서 this에 대한 타입 제공하기

오버로딩 타입보다는 조건부 타입 사용하기

의존성 분리를 위해 미러 타입 사용하기

테스팅 타입의 함정에 주의하기

soyi47 commented 1 year ago

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

타입스크립트가 태동하던 2010년에 자바스크립트는 개선해야 할 부분이 많았다. (당시에는 클래스, 데코레이터, 모듈 시스템 같은 기능이 없었다.) 따라서 타입스크립트도 독립적으로 개발한 기능들을 포함시켜야 했다. 시간이 흐르며 TC39(자바스크립트를 관장하는 표준 기구)는 부족했던 점을 대부분 내장 기능으로 추가했다. 이는 타입스크립트 초기 버전에서 독립적으로 개발했던 기능과 호환성 문제를 발생시켰다. 타입스크립트 진영에서는 이에 대응하기 위한 전략을 선택해야 했다.

  1. 타입스크립트 초기 버전의 형태를 유지하기 위해 자바스크립트 신규 기능을 변형해서 끼워맞추기
  2. 자바스크립트 신규 기능을 그대로 채택하고 타입스크립트 초기 버전과 호환성을 포기하기

타입스크립트 팀은 대부분 두 번째 전략을 선택했다. TC39는 런타임 기능을 발전시키고, 타입스크립트 팀은 타입 기능만 발전시킨다는 원칙을 세우고 지켜오고 있다.

그러나 이 원칙이 세워지기 전에 이미 사용되고 있던 몇 가지 기능이 있다. 이는 타입 공간(타입스크립트)와 값 공간(자바스크립트)의 경계를 혼란스럽게 만들기 때문에 사용하지 않는 것이 좋다.

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

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

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

public, protected, private 같은 접근 제어자는 타입스크립트 키워드라 컴파일 후에는 사라진다. 이는 컴파일 시점에만 오류를 표시할 뿐 런타임에는 아무 효력이 없다. 확실히 데이터를 감추고 싶다면 클로저를 사용해야 한다.

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

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

옛날 버전 자바스크립트 코드를 최신 버전 자바스크립트 코드로 바꾸는 작업은 타입스크립트로 전환하는 작업의 일부로 볼 수 있다. 마이그레이션을 어디서부터 해야할 지 몰라 막막하다면 최신 버전 자바스크립트로 바꾸는 작업부터 시작해보자. ES6부터 도입된 주요 기능 몇 가지를 간략히 알아보자.

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

@ts-check 지시자를 사용하여 타입스크립트 전환 시에 어떤 문제가 발생하는지 미리 시험해 볼 수 있다. @ts-check 지시자를 사용하여 타입 체커가 파일을 분석하고 발견된 오류를 보고하도록 지시한다.

// @ts-check
const person = { first: 'Grace', last: 'Hopper' };
2 * person.first
// 에러! 산술 연산 오른쪽은 'any', 'number', 'bigint' 또는 열거형 형식이어야 합니다.

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

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

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