[이펙티브 타입스크립트] 아이템21 ~ 아이템24

Yongwoo Cho·2022년 6월 2일
0

TIL

목록 보기
79/98
post-thumbnail

[아이템21] 타입 넓히기

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

넓히기의 과정을 제어하는 방법

  • const 사용
    재할당이 불가능 하기 때문에 타입스크립트는 좁은 타입으로 추론이 가능하다.
    ❗ 객체의 경우 타입스크립트의 넓히기 알고리즘은 각 요소를 let으로 할당된 것처럼 다룬다.

타입스크립트의 기본 동작을 재정의(타입 추론의 강도를 직접 제어) 하는 방법

  1. 명시적 타입 구문 제공
const v: { x: 1 | 3 | 5 } = {
  x: 1,
};
  1. 타입 체커에게 추가적인 문맥 제공(ex 함수의 매개변수로 값을 전달)
  2. const 단언문 사용
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;}

👉 값 뒤에 as const를 작성하면 타입스크립트느 최대한 좁은 타입으로 추론한다.

[아이템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를 사용한 타입 좁히기
function contains(text: string, search: string | RegExp) {
  if (search instanceof RegExp) {
    search; // type : RegExp
    return !!search.exec(text);
  }
  search; // type : string
  return text.includes(search);
}
  • 속성 체크로 타입 좁히기
interface A {
  a: number;
}
interface B {
  b: number;
}
function pickAB(ab: A | B) {
  if ("a" in ab) ab; // type : A
  else ab; // type : B

  ab; // type : A | B
}
  • Array.isArray 같은 내장 함수로 타입 좁히기
function contains(text: string, terms: string | string[]) {
  const termList = Array.isArray(terms) ? terms : [terms];
  termList; // type : string[]
}
  • 사용자 정의 타입 가드를 통한 타입 좁히기
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 = {}; // ❌ ~'{}' 형식에 '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; // ❌ ~~~ '{}' 형식에 'name'속성이 없습니다.

// 객체 전개 연산자 (...) 사용
const namedPoint = { ...pt, ...id };
namedPoint.name; // 정상

객체에 조건부로 속성을 추가하는 방법

declar let hasMiddle: boolean;
const firstLast = {first: 'Harry', last: 'Truman'};
const president = {...firstAlst, ...(hasMiddle ? { middle: 'S' } : {})};

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

[아이템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'일 수 있습니다.
    }
  }
}

polygon.bbox를 별도의 box라는 별칭을 만들었기 때문에 제어 흐름 분석을 방해

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const box = polygon.bbox;
  if (box) {
    if (pt.x < box.x[0]) {
      // 정상
    }
  }
}

에러는 해결했지만 box와 bbox는 같은 값인데 다른 이름을 사용하여 코드를 읽는 사람에게 문제가 될 수 있다.

function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
  const { bbox } = polygon;
  if (bbox) {
    const { x, y } = bbox;
    if (pt.x < x[0]) {
      // 정상
    }
  }
}

👍 객체 비구조화를 이용하면 보다 간결한 문법으로 일관된 이름을 사용할 수 있다.

❗ 주의할 점 : x, y가 선택적 속성일 경우에 속성 체크가 필요하다. (null 체크)

profile
Frontend 개발자입니다 😎

0개의 댓글