객체(Object)
TypeScript의 객체(object) 타입은 객체의 형태(shape)를 정의합니다.
그러나 객체 타입만으로는 ({}로 만든) 간단한 객체와 (new를 사용해 만든) 복잡한 객체를 구분할 수 없습니다. 이는 JavaScript가 구조 기반 타입(structural type)을 갖도록 설계되었기 때문입니다. 따라서 TypeScript도 이름 기반 타입(nominal type) 스타일보다는 JavaScript 스타일을 선호합니다.
구조 기반 타입화
구조 기반 타입화에선 객체의 이름에 상관없이 객체가 어떤 프로퍼티를 갖고 있는지를 따집니다.(이름 기반 타입에선 이름을 따집니다.) 일부 언어에서는 덕 타이핑(duck typing)이라고 합니다(겉표지만 보고 책을 판단하지 않는 것과 같은 원리).
object
a type that represents the non-primitive type
primitive type이 아닌 것을 나타내고 싶을 때 사용하는 타입입니다.
non-primitive type
not number, string, boolean, bigint, symbol, null, or undefined.
TypeScript에서 객체 서술
값을 object로 선언
첫 번째 방법은 값을 object로 선언하는 것입니다.
let a: object = {
b: 'x'
}
a.b 와 같이 b에 접근하면, error TS2339: 'b' 프로퍼티는 'object'에 존재하지 않다는 에러가 발생합니다.
이처럼 값의 프로퍼티에도 접근할 수 없다면 object로 타입을 지정할 이유는 없습니다.
사실 object는 any보다 조금 더 좁은 타입입니다. object는 서술하는 값에 관한 정보를 거의 알려주지 않으며, 값 자체가 JavaScript 객체라고 그리고 null이 아니라고 말해줄 뿐입니다.
객체 리터럴 문법
두 번째 방법은 객체 리터럴 문법입니다. (타입 리터럴과 혼동하면 안 됩니다.)
let a = {
b: 'x'
// { b: string}
}
let b = {
c: {
d: 'f'
}
} // {c: {d: string} }
TypeScript가 c의 형태를 추론하게 하거나 중괄호({}) 안에서 명시적으로 타입을 묘사할 수 있습니다.
let a: { b : number } = {
b: 12
} // {b: number}
객체를 const로 선언할 때의 타입 추론
객체를 const로 선언하면 어떻게 될까요?
const a: { b: number } = {
b: 12
} // 여전히 {b: number}
TypeScript가 b를 리터럴 12가 아닌 number로 추론했습니다. 이를 통해 const를 사용하느냐 let을 사용하느냐에 따라 TypeScript가 number나 string타입을 어떻게 추론하는지 알게 되었습니다.
객체 리터럴 문법은 "이런 형태의 물건이 있어"라고 말합니다. 이 물건은 객체 리터럴 또는 클래스일 수 있습니다.
let c: {
firstName: string
lastName: string
} = {
firstName: 'linda',
lastName: 'cozy'
}
class Person {
constructor(
public firstName: string, // public은 this.firstName = firstName을 단축한 것입니다.
public lastName: string
) { }
}
c = new Person('andy', 'kim') // OK
{ firstName: string, lastName: string }은 객체의 형태를 묘사하여 객체 리터럴과 클래스 인스턴스 모두 이 형태를 만족하므로 TypeScript는 Person을 c로 할당하는 동작을 허용합니다.
확실한 할당(definite assignment)
변수를 선언하고 나중에 초기화하는 상황에서 TypeScript는 변수를 사용하기 전에 값을 할당하도록 강제합니다.
let i: number;
let j = i * 2; // error TS2454: 할당하기 전 변수 'i'를 사용
TypeScript는 타입을 명시하지 않아도 이 규칙을 잘 적용합니다.
let i;
let j = i * 2; // error TS2532: 'undefined' 객체
기본적으로 TypeScript는 객체 프로퍼티에 엄격한 편입니다.
예를 들면, 객체에 string 타입의 s라는 프로퍼티가 있어야 한다고 정의하면, Typescript는 오직 a만 기대합니다. 따라서 a가 없거나 다른 추가 프로퍼티가 있으면 에러를 발생시킵니다.
그렇다면 어떤 프로퍼티는 선택형이고, 예정에 없던 프로퍼티가 추가될 수 있다고 타입스크립트에 알려줄 수 있을까요?
let a: {
b: number
c?: string
[key: number]: boolean
}
- a는 number 타입의 프로퍼티 b를 포함합니다.
- a는 string 타입의 프로퍼티 c를 포함할 수도 있습니다.
- a는 boolean 타입의 값을 갖는 number 타입의 프로퍼티를 여러 개 포함할 수 있습니다.
a에 할당할 수 있는 객체 타입은 아래와 같습니다.
a = {b: 1};
a = {b: 1, c: undefined};
a = {b: 1, c: 'd'};
a = {b: 1, 10: true};
a = {b: 1, 10: true, 20: false};
a = {10: true}; // error TS2741: b프로퍼티가 없음
a = {b: 2, 33: 'red'}; // error TS2741: 'string' 타입은 'boolean' 타입에 할당할 수 없음
인덱스 시그니처(index signature)
[key: T]: U 같은 문법을 인덱스 시그니처라 부르며, TypeScript에 어떤 객체가 여러 키를 가질 수 있음을 알려줍니다. 즉 "이 객체에서 모든 T 타입의 키는 U 타입의 값을 갖는다"라고 해석할 수 있습니다.
인덱스 시그니처를 활용하면, 명시적으로 정의한 키 외에 다양한 키를 객체에 안전하게 추가할 수 있습니다.
기억해야 할 한 개의 규칙이 있는데, 인덱스 시그니처의 키(T)는 반드시 number나 string 타입에 할당할 수 있는 타입이어야 한다는 것입니다.
인덱스 시그니처의 키 이름은 원하는 이름을 가져다가 바꿔도 무방합니다. 즉, 굳이 key가 아니어도 됩니다.
readonly
객체 타입을 정의할 때 선택형(?)만 사용할 수 있는 것은 아닙니다.
필요하면 readonly 한정자를 이용해 특정 필드를 읽기 전용으로 정의할 수 있습니다.(정의한 필드에 초기값을 할당한 다음에는 그 값을 바꿀 수 없기 때문에 객체 프로퍼티에 const를 적용한 듯한 효과를 냅니다.).
let user: {
readonly firstName: string
} = {
firstName: 'Bae'
}
user.firstName // string
user.firstName = 'Bae with an e' // error TS2540: 'firstName'은 읽기 전용 프로퍼티이므로 할당할 수 없음
빈 객체 타입({})
객체 리터럴 표기법에는 빈 객체 타입({})이라는 특별한 상황이 존재합니다.
null과 undefined를 제외한 모든 타입은 빈 객체 타입에 할당할 수 있으나, 이는 사용하기 까다롭게 만듭니다. 때문에 가능한 한 빈 객체는 피하는 것이 좋습니다.
객체: Object
{}과 비슷한 방법이며, 마찬가지로 가능하면 사용하지 않는 것이 좋습니다.
정리
TypeScript에서 객체를 정의하는 방법은 아래처럼 4 가지로 요약할 수 있습니다.
- 객체 리터럴 또는 형태라 불리는 표기법({a: string}) : 객체가 어떤 필드를 포함할 수 있는지 알고 있거나 객체의 모든 값이 같은 타입을 가질 때 사용합니다.
- 빈 객체 리터럴 표기법({}): 이 방법은 사용하지 않는 것이 좋습니다.
- object 타입: 어떤 필드를 가지고 있는지는 관심이 없고, 그저 객체가 필요할 때 사용합니다.
- Object 타입: 이 방법은 사용하지 않는 것이 좋습니다.
'👶 TypeScript' 카테고리의 다른 글
Tuple (0) | 2023.01.08 |
---|---|
Array (0) | 2023.01.08 |
null & undefined (0) | 2023.01.07 |
symbol (0) | 2023.01.07 |
string (0) | 2023.01.07 |