타입스크립트 스터디4회차

안윤경·2023년 1월 7일

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

모든변수에 타입을 선언 하는 것은 매우 비생산적입니다.
타입 추론이 가능하다면 명시적인 타입 선언은 필요하지 않기 때문입니다.

let x:number = 12;
let x = 12; //타입추론이 가능하므로 굳이 타입선언을 해줄 필요 없음

더 복잡한 객체/배열 인 경우도 타입 추론이 가능합니다.

const person = {
  name: "Truth",
  born: {
    where: "Seoul",
    when: "c.1919",
  },
  died:{
    where: "Seoul",
    when: "c.1919",
  }
}//객체

function square(nums: number[]){
  return nums.map(x =>x *x);
}
const squares = square([1,2,3]) // number[];

비구조화 할당문은 모든 지역 변수의 타입이 추론되도록 합니다.
여기에 추가적으로 명시적 타입 구문을 넣을 시 불필요한 타입 선언으로 코드가 번잡해 질 수 있음!

function logProduct(product:Product){
const {id,name,price}= prodict //비구조화 할당
}

타입스크립트는 최종 사용처까지 고려하지 않고 타입스크립트에서 변수와 타입은 일반적으로 처음 등장할 때 결정됩니다.

단! 라이브러리를 사용할 때 타입 정보가 기본적으로 제공된다면 콜백함수 매개변수 타입은 자동으로 추론딥니다.

  • 타입을 명시해야하는 상황이 있습니다.그중 하나는 객체리터럴을 정의할 때입니다.

    타입구문을 제거하면 잉여 속성 체크가 동작하지 않아서 선언한 곳이 아닌 사용되는 곳에서 타입 오류가 발생합니다.
    =>타입구문을 쓴다면 실수가 발생한 그부분에 오류를 표시해줍니다.

++linter를 사용한다면 no-inferrable-types를 사용해서 모든 타입구문이 정말 필요한지 확인 할 수 있습니다

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

중복되는 변수를 사용할 경우 타입 정의 시 혼란이 올 수 있습니다.

아이템21.타입 넓히기

  • 타입 넓히기란?

타입스크립트에서의 과정으로 지정된 단일 값을 가지고 할당 가능한 값들의 집합을 유추해야 한다는 뜻입니다

  • 문제가 되는 이유?
    타입 넓히기가 진행될 때 주어진 값으로 추론 가능한 타입이 여러개이기 때문에 과정이 상당히 모호하기 때문이다.

넓히기 과정 제어하는 방법


1.const

let대신 const로 변수를 선언시 재할당이 불가능 하기 때문에 타입스크립트는 좁은 타입으로 추론이 가능하다.

객체의 경우 타입스크립트의 넓히기 알고리즘은 각 요소를 let으로 할당된 것처럼 다룬다.

const x ='x'//타입이 "x"
let vec ={x:10,y:20,z:30} 

2.명시적 타입 구문을 제공

const v: { x: 1 | 3 | 5 } = {
  x: 1,
};

3.타입체커에 추가적은 문맥을 제공

const v1 = {
  x: 1,
  y: 2,
}; // type : {x: number; y: number;}
const v2 = {
  x: 1 as const,
  y: 2,
}; // type : {x: 1; y: number;}
const v3 = {
  x: 1,
  y: 2,
} as const; // type : {readonly x: 1; readonly y: 2;}

아이템22. 타입 좁히기

  • 좁히기란?

    타입스크립트가 넓은 타입으로부터 좁은 타입으로 진행하는 과정. ex) null 체크

const el = document.getElementById("foo"); // type : HTMLElement | null
if (el) {
  // type : HTMLElement
  el.innerHTML = "1";
} else {
  // type : null
  alert("no element");
}

if (!el) throw new Error("no element"); // type : null
el.innerHTML = "1"; // type : HTMLElement

이외에도 instanceof,속성 체크,Array.isArray 같은 내장 함수등으로 타입을 좁힐 수 있다.
또다른 방법으로는 명시적'태그'를 붙이는 방법이다.
=>사용자 정의 타입 가드 이용하는 방법이 있다.

function isInputElement(el: HTMLElement): el is HTMLInputElement {
  return "value" in el;
}
function getElementContent(el: HTMLElement) {
  if (isInputElement(el)) {
    el; // type : HTMLInputElement
    return el.value;
  }
  el; // type : HTMLElement
  return el.textContent;
}

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

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

// 객체를 반드시 제각각 나눠서 만들어야 할땐 단언문을 사용하자
const pt = {} as Point;
pt.x = 3;
pt.y = 4;
const pt = { x: 3, y: 4 };
const id = { name: "Pythagoras" };
const namedPoint = {};
Object.assign(namedPoint, pt, id);
namedPoint.name; // x ~~~ '{}' 형식에 'name'속성이 없습니다.

// 객체 전개 연산자 (...) 사용
const namedPoint = { ...pt, ...id };
namedPoint.name; // 정상
declar let hasMiddle: boolean;
const firstLast = {first: 'Harry', last: 'Truman'};
const president = {...firstlast, ...(hasMiddle ? { middle: 'S' } : {})};

const president:{
  middle?: string;
  first: string;
  last: string;
}

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

아이템24.일관성 있는 별칭 사용하기

타입스크립트에서 식별자를 일관성 없이 사용하는 것은 지양해야 한다.
그 이유는 타입 좁히기와 관련이 있다.

interface Coordinate {
  x: number;
  y: number;
}
interface BoundingBox {
  x: [number, number];
  y: [number, number];
}
interface Polygon {
  exterior: Coordinate[];
  holes: Coordinate[][];
  bbox?: BoundingBox;
}

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const box = polygon.bbox;
  if (polygon.bbox) {
    if (pt.x < box.x[0]) {
      // 객체가 'undefined'일 수 있습니다.
    }
  }
}

코드는 작동하지만 편집기에서 오류로 표시됩니다
why?

그 이유는 polygon.bbox를 별도의 box라는 별칭을 만들었고,
이 부분이 제어 흐름 분석을 방해했기 때문입니다.

polygon.bbox의 타입은 정제했지만 box는 그렇지 않기 때문에 오류가 발생한 것!

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

콜백보다는 프로미스나 async/await를 사용해야하는 이유?

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

프로미스를 사용하면 타입스크립트의 모든 타입 추론이 제대로 동작한다

✔ 프로미스를 생성하기보다는 async/await를 사용해야 하는 이유?

  • 일반적으로 더 간결하고 직관적임
  • async 함수는 항상 프로미스를 반환하도록 강제됨
  • 콜백이나 프로미스를 사용하면 실수로 반동기 코드를 작성할 수 있지만 async를 사용하면 항상 비동기 코드를 작성한다.
  • async 함수에서 프로미스를 반환하면 또 다른 프로미스로 래핑되지 않는다. 반환 타입은 Promise<Promise>가 아닌 Promise가 된다.

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

변수를 let / const 키워드를 이용하여 선언할 때 타입 추론

//let의경우
let language = "JavaScript";

type LaunguageType = typeof language;
// 타입은 string


//const의 경우
const language = "JavaScript";

type LaunguageType = typeof language;
// 타입은 "JavaScript"

해결방법

  • 타입 선언에서 language의 가능한 값을 제한하기
let language: Language = "Javascript";
setLanguage(language); // 정상
  • language를 상수로 만들기
const language = "Javascript";
setLanguage(language); // 정상

튜플의 경우

function panTo(where: [number, number]) {
  /* ... */
}

panTo([1, 2]); // 정상
const loc = [1, 2];
panTo(loc); //  'number[]' 형식의 인수는 '[number, number]' 형식의 매개변수에 할당될 수 없습니다.

const와 as const의 차이?

const 키워드를 통해 선언한 변수는 얕은(shallow) 상수라고 한다.

선언을 할 때 변수를 깊은(deeply) 상수로 선언을 하려면 as const 를 사용한다.

값이 내부까지 상수라는 사실을 타입스크립트에게 알려준다.
단 위처럼 할 시 readonly로 인해 너무 과하게 정확해집니다

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

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

profile
프론트엔드 개발자 안윤경입니다

0개의 댓글