아이템 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 잉여 속성 체크)
- 객체 전개 연산자(
...
)를 이해한다.