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

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

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

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

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

const pt = {};
pt.x = 3;
pt.y = 4;
» tsc example.ts

example.ts:2:4 - error TS2339: Property 'x' does not exist on type '{}'.

2 pt.x = 3;
     ~

example.ts:3:4 - error TS2339: Property 'y' does not exist on type '{}'.

3 pt.y = 4;
     ~

(const pt = {}) pt 는 {} 값을 기준으로 타입 추론된다. 따라서 존재하지 않는 속성을 추가할 수 없다. (= 잉여 속성 체크(?))

다만, 꼭 이렇게 속성을 따로따로 만들어야 한다면, 타입 단언문(as)을 사용할 수 있다.

interface Point { x:number; y:number; }
const pt = {} as Point;
pt.x = 3;
pt.y = 4;

" 물론 이 경우에도 선언할 때 객체를 한꺼번에 만드는 게 더 낫습니다. (아이템 9) “



객체 전개 연산자 : ... #

요약 : 객체에 속성을 추가(= 타입스크립트가 새로운 타입을 추론할 수 있게)할 수 있다.


‘작은 객체’들을 조합해서 ‘큰 객체’를 만들어야 하는 경우에도 여러 단계를 거치는 것은 좋지 않다.

const pt = {x: 3, y: 4};
const id = {name: 'Pythagoras'};

const namedPoint = {};
Object.assign(namedPoint, pt, id);
console.log(namedPoint.name); // Error : '{}' 형식에 'name' 속성이 없습니다. (Property 'name' does not exist on type '{}')

‘객체 전개 연산자’를 사용하면 큰 객체를 한꺼번에 만들어 낼 수 있다.

const pt = {x: 3, y: 4};
const id = {name: 'Pythagoras'};

const namedPoint = {...pt, ...id};
console.log(namedPoint.name); // 출력 : Pythagoras
» cat example.js

var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var pt = { x: 3, y: 4 };
var id = { name: 'Pythagoras' };
var namedPoint = __assign(__assign({}, pt), id);
console.log(namedPoint.name);

‘객체 전개 연산자’를 사용하면 (타입 걱정 없이)필드 단위로 객체를 생성할 수도 있다.

이때 모든 업데이트마다 ‘새 변수’를 사용하여 각각 ‘새로운 타입’을 얻도록 하는게 중요하다.

interface Point { x:number; y:number; }

const pt0 = {};
const pt1 = {...pt0, x:3};
const pt: Point = {...pt1, y:4}; // 정상
» cat example.js

var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var pt0 = {};
var pt1 = __assign(__assign({}, pt0), { x: 3 });
var pt = __assign(__assign({}, pt1), { y: 4 }); // 정상

조건부로 속성을 추가할 수도 있다.

declare let hasMiddle: boolean;

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

/*
const president: {
    middle?: string;
    first: string;
    last: string;
}
*/
» cat example.js

var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var firstLast = { first: 'Harry', last: 'Truman' };
var president = __assign(__assign({}, firstLast), (hasMiddle ? { middle: 'S' } : {}));

declare let hasDates: boolean;

const nameTitle = {name: 'kk', title: 'ph'};
const pharaoh = {
    ...nameTitle,
    ...(hasDates ? {start: -2589, end: -2566} : {})
};

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

선택적 필드(?:) 가 아닌 이유 : 2개 이상의 속성일 경우에는 유니온으로 처리된다.

” 이 경우는 start와 end가 항상 함께 정의됩니다. 이 점을 고려하면 유니온을 사용하는 게 가능한 값의 집합을 더 정확히 표현할 수 있습니다. (아이템 32) “


(위 예시) 선택적 필드 방식으로 표현하려면 (다음과 같이)헬퍼 함수를 사용할 수 있다.

이 부분은 아직 이해 X
선택적 필드 방식으로 표현할 수 있다 정도만 이해하고 넘어간다.

function addOptional<T extends object, U extends object>(
    a: T, b: U | null
): T & Partial<U> {
    return {...a, ...b};
}

const pharaoh = addOptional(
    nameTitle,
    hasDates ? {start: -2589, end: -2566} : null
)

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



요약 & 정리 #

  • (되도록) 한꺼번에 객체를 생성한다.
    • 타입 추론(with 잉여 속성 체크)
  • 객체 전개 연산자(...)를 이해한다.

참고 #