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

변수의 값은 변경될 수 있지만, 타입스크립트의 타입은 일반적으로 변경되지 않는다.

→ 일부 자바스크립트 패턴을 타입스크립트로 모델링하는게 쉬워진다.

즉, 객체를 생성할 때는 속성을 하나씩 추가하기보다는 여러 속성을 포함해서 한꺼번에 생성해야 타입 추론에 유리하다.

📍 예시

⭐️ 자바스크립트에서 객체를 생성하는 방법

🔗 2차원 점을 표현하는 객체를 생성하는 예제를 알아보자.

타입스크립트에서는 각 할당문에 오류가 발생한다.

const pt = {};
//    ^? const pt: {}
pt.x = 3;
// ~ Property 'x' does not exist on type '{}'
pt.y = 4;
// ~ Property 'y' does not exist on type '{}'

pt 타입은 {} 값을 기준으로 추론된다. 존재하지 않는 속성은 추가할 수 없다.

🔗 Point 인터페이스를 정의한다면 오류가 다음처럼 바뀐다.

interface Point { x: number; y: number; }
const pt: Point = {};
   // ~~ Type '{}' is missing the following properties from type 'Point': x, y
pt.x = 3;
pt.y = 4;

이런 문제들을 객체를 한번에 정의하면 해결할 수 있다.

const pt = {
  x: 3,
  y: 4,
}; // OK

🔗 객체를 반드시 제각각 나눠서 만들어야 한다면, 타입 단언문을 사용해 타입 체커를 통과하게 할 수 있다.

const pt = {} as Point;
//    ^? const pt: Point
pt.x = 3;
pt.y = 4;  // OK

이 경우도, 선언할 때 객체를 한꺼번에 만드는게 더 낫다.

const pt: Point = {
  x: 3,
  y: 4,
};

🔗 작은 객체들을 조합해서 큰 객체를 만들어야하는 경우에도 한꺼번에 만드는 것이 좋다.

// 여러 단계를 거치는 경우
const pt = {x: 3, y: 4};
const id = {name: 'Pythagoras'};
const namedPoint = {};
Object.assign(namedPoint, pt, id);
namedPoint.name;
        // ~~~~ Property 'name' does not exist on type '{}'

다음과 같이, '객체 전개 연산자' ... 을 사용하여 큰 객체를 한꺼번에 만들어보자.

const namedPoint = {...pt, ...id};
//    ^? const namedPoint: { name: string; x: number; y: number; }
namedPoint.name;  // OK
//         ^? (property) name: string

🔗 객체 전개 연산자를 사용하면 타입 걱정 없이 필드 단위로 객체를 생성할 수 있다. 이 때 모든 업데이트마다 새 변수를 사용하여 각각 새로운 타입을 얻도록 하는게 중요하다.

const pt0 = {};
const pt1 = {...pt0, x: 3};
const pt: Point = {...pt1, y: 4};  // OK

이 방법은 간단한 객체를 만들기 위해 우회했지만, 객체에 속성을 추가하고 타입스크립트가 새로운 타입을 추론할 수 있게 해 유용하다.

🔗 타입에 안전한 방식으로 조건부 속성을 추가하려면, 속성을 추가하지 않는 null 또는 {}으로 객체 전개를 사용하면 된다.

declare let hasMiddle: boolean;
const firstLast = {first: 'Harry', last: 'Truman'};
const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
//    ^? const president: {
//         middle?: string;
//         first: string;
//         last: string;
//       }
// or: const president = {...firstLast, ...(hasMiddle && {middle: 'S'})};

편집기에 president 속성을 보면, 타입이 선택적 속성을 가진 것으로 추론된다는 것을 알 수 있다.

🔗 전개 연산자로 한꺼번에 여러 속성을 추가할 수도 있다.

declare let hasDates: boolean;
const nameTitle = {name: 'Khufu', title: 'Pharaoh'};
const pharaoh = { ...nameTitle, ...(hasDates && {start: -2589, end: -2566})};
//    ^? const pharaoh: {
//         start?: number;
//         end?: number;
//         name: string;
//         title: string;
//       }

pharaoh의 타입이 유니온으로 추론된다.

const pharaoh: {
    start?: number;
    end?: number;
    name: string;
    title: string;
} | {
    name: string;
    title: string;
}

startend가 선택적 필드이기를 원했다면, 이런 결과가 당황스러울 수 있다. 이 타입에서는 start를 읽을 수 없다.

pharaoh.start
// {name: string; title: string;} 형식에 'start' 속성이 없다.

이 경우에는 start와 end가 항상 함께 정의된다. 이 점을 고려하면 유니온을 사용하는게 가능한 값의 집합을 더 정확히 표현할 수 있다.

유니온 보다는 선택적 필드가 다루기에는 쉽다.

📍 정리

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

📍 요약

  • 속성을 제각각 추가하지 말고 한꺼번에 객체로 만들어야 한다. 안전한 타입으로 속성을 추가하려면 객체 전개({...a, ...b})를 사용하면 된다.

  • 객체에 조건부로 속성을 추가하는 방법을 익히도록 하자.

Last updated