
객체 리터럴
각자의 타입이 있는 키와 값의 집합
{…} 구문을 사용해서 객체 리터럴을 생성하면 해당 속성 기반으로 객체 타입을 유추한다.
{a: 1, b: 2}
// 객체 값과 동일한 속성명과 원시 타입을 가진다.
{a: number, b: number}
const poet = {
name: 'Mary',
born: 1995
}
// 값의 속성에 접근
poet.name; // 타입 string
poet[born]; // 타입 number
// 다른 속성 이름으로 접근하면 에러
poet.end;
객체 타입 선언
기존 객체에서 타입을 유추하는 것 대신 객체의 타입을 명시적으로 선언
let poet = {
name: string;
born: number;
}
poet = {
name: 'Mary',
born: 1995
}
별칭 객체 타입
객체의 타입에 별칭을 할당해서 사용하는 방법
일반적으로 타입스크립트 프로젝트는 객체 타입을 설명할 때 인터페이스 키워드를 사용하는 것을 선호하며 별칭객체타입과 인터페이스는 거의 동일
interface Poet {
name: string;
born: number;
}
let poetLater: Poet;
poetLater = {
name: 'Mary',
born: 1995
}
타입스크립트는 객체 타입을 체크할 때 좀 더 엄밀한 속성 검사를 진행하게 된다.
TS 타입시스템은 구조적으로 타입화되어 있다. 타입을 충족한 모든 값을 해당 타입의 값으로 사용할 수 있음
즉 매개 변수나 변수가 특정 객체타입으로 선언되면, 타입스크립트에 어떤 객체를 사용하든 해당 속성이 있어야 한다.( 집합관계를 취한다)
위 정의가 이해가 안될 수 있다. 코드로 이해해보자!
interface A {
id : number
}
interface B {
id : number
}
let a : A;
let b : B = {
id: 5,
}
a = b //OK
a 변수는 A 타입으로 선언 되었지만 ‘구조적으로 동일’한 B 타입의 객체를 할당 하더라도 에러를 내지 않음
interface A {
id : number
}
interface B {
id : number
name : string
}
let a : A;
let b : B = {
id: 5,
name: 'june'
}
a = b // OK
B 타입에는 A 타입에 없는 name 속성이 있지만 해당 코드도 통과한다.
interface A {
id : number
}
const a : A = {
id: 1,
name: 'a' // Error
}
사용 검사
객체 타입으로 애너테이션된 위치에 값을 제공할 때, 할당하는 값에는 객체 타입의 필수 값이 있는지, 타입이 맞는지 검사하는 것
interface Name {
first: string;
last: string;
}
const a: Name = {
first: 'aaa',
last: 'aaa'
}
// (1) 두 가지 속성이 모두 없는 객체는 사용할 수 없음
const b: Name = {
~~~
// Error: 'last' is missing / required in Name
first: 'aaa',
}
// (2) 일치하지 않는 타입도 허용되지 않음
const b: Name = {
first: 'aaa',
last: 1234
}
초과 속성 검사
변수가 객체 타입으로 선언되고, 초기값에 객체타입에서 정의된 것보다 많은 필드가 있으면 오류 발생
interface Poet {
name: string;
born: number;
}
const poet: Poet = {
name: 'Mary',
born: 1995
}
const ExtraProperty: Poet = {
activity: 'walking',
~~~~~~~~~ Error
name: 'Mary',
born: 1995
};
interface Avengers {
name: string;
}
let hero: Avengers;
hero = { name: 'Captain', location: 'Pangyo' }; // Err왜?
객체 리터럴을 변수에 직접 할당할 때나 인수로 전달할 때, 초과 프로퍼티 검사 (excess property checking)를 받게 된다.
그래서 만약 객체 리터럴이 "대상 타입 (target type)"이 갖고 있지 않은 프로퍼티를 갖고 있으면, 당연하게도 에러가 발생된다.
interface Avengers {
name: string;
}
/*
type Avengers = {
name: string;
};
let hero: {
name: string;
};
*/
let hero: Avengers;
// hero = { name: 'Captain', location: 'Pangyo' };
let tmp = { name: 'Captain', location: 'Pangyo' };
hero = tmp; // OK
console.log(hero); // { name: 'Captain', location: 'Pangyo' }
왜?
그러나 객체를 다른 변수에 할당하게 되면 변수 tmp 는 초과 프로퍼티 검사를 받지 않기 때문에(우회하기 때문에), 컴파일러는 에러를 주지 않는 것이다.
직접 초기값을 할당하지 않는경우, 즉 참조에 의한 초기값 할당은 타입 시스템의 초기속성검사에서 제외된다.
let tmp = { name2: 'Captain', location: 'Pangyo' };
hero = tmp; // Error
왜?
그렇다고 해서 다음과 같이 인터페이스에 정의된 공통 객체 프로퍼티가 없으면 초과 프로퍼티 검사 회피든 뭐든 무조건 에러가 발생된다.
interface Poet {
name: string;
born: number;
}
interface IExtraProperty {
name: string;
born: number;
activity: string;
}
const ExtraProperty: IExtraProperty = {
activity: 'walking',
name: 'Mary',
born: 1995
}
const poet: Poet = ExtraProperty;
poet.activity;
~~~~~~~~~~
Property 'activity' does not exist on type 'Poet'.
// 함수로 된 예제중첩된 객체 타입
객체는 다른 객체의 멤버로 중첩될 수 있으며, 객체 타입도 중첩된 객체 타입을 나타낼 수 있다.
interface Author {
first: string;
last: string;
}
interface Poem {
name: string;
author: Author;
}
const poem: Poem = {
name: 'Mary',
author: {
name: 'Sylvia' // Error
}
}
선택적 속성
타입의 속성 애너테이션 :앞에 ?를 추가하여 선택적 속성임을 나타낼 수 있다.
interface Poem {
name: string;
author: undefined;
}
const poem: Poem = {
name: 'Mary' // OK
}
Property 'author' is missing in type '{ name: string; }' but required in type 'Poem'.(2741)
undefined를 포함한 유니언 타입과 헷갈리지 말기! 선택적 속성은 존재하지 않아도 된다. 하지만 필수로 선언된 속성과 | undefined 는 그 값이 undefined 일지라도 반드시 존재해야 함속성이 조금 다른, 하나 이상의 서로 다른 객체의 타입을 설명할 수 있어야 하고 (유니언)
또한 속성값을 기반으로 타입을 좁혀야 할 수도 있다. (내로잉)
객체도 원시 타입을 유니언으로 정의하는 것 처럼 동일하게 할 수 있다.
const poem = Math.random() > 0.5
? {name: 'a', pages: 7}
: {name: 'b', rhymes: true}
여러 타입 중 하나가 될 수 있는 초기값이 주어지면 객체 타입 유니언으로 유추
const poem = Math.random() > 0.5
? {name: 'a', pages: 7}
: {name: 'b', rhymes: true}
// 타입
//{
// name: string;
// pages: number;
// rhymes?: undefined; 초기값 없는 선택적 타입이지만 구성요소로 주어짐
//}
//|
//{
// name: string;
// pages?: undefined; 초기값 없는 선택적 타입이지만 구성요소로 주어짐
// rhymes: boolean;
//}
poem.name; // string
poem.pages; // number | undefined
poem.rhymes; // boolean | undefined
이렇게 되면 undefined인 속성에 접근하여 잠재적인 오류를 발생시킬 수 있는 경우가 발생할 수 있으며
또한 오류 메세지의 가독성 또한 좋지 못하다.
잠재적으로 존재하지 않는 객체의 멤버에 대한 접근을 제한함으로써 코드의 안전을 높임
interface PoemWithRhymes {
name: string;
rhymes: boolean;
}
interface PoemWithPage {
name: string;
pages: number;
}
interface Poem = PoemWithPage; | PoemWithRhymes;
const poem: Poem = Math.random() > 0.5
? {name: 'a', pages: 7}
: {name: 'b', rhymes: true};
poem.name; //OK
poem.pages; // Error -> 속성 검사 시 page가 존재한다는 보장 없음
poem.rhymes; // Error -> 속성 검사 시 rhymes가 존재한다는 보장 없음
에러를 통해 없을 수 있는 속성에 대해서 속성 검사가 제대로 이루어짐.
그리고 보장된 속성 값에 접근하기 위해서는 내로잉을 통해 접근해야 한다.
(6) 객체 타입 내로잉
타입 가드를 통해서 타입을 내로잉
(instance of, typeof, use in keyword, discriminated Union etc..)
if (poem.pages){
~~~~~~~~~~~
Error: Property 'pages' does not exist on type 'poem'.
...
}else{
...
}
// 타입가드가 참여부를 확인하기 전에 존재하지 않는 속성에 접근하려고 시도하면 타입오류로 간주
in 연산자)if ('pages' in poem){ // 'in' keyword를 이용한 type narrowing
poem.pages;
}else{
poem.rhymes;
}
객체 타입을 객체 안의 속성으로 저장하여 객체 속성이 객체의 타입을 나타내게 끔 하는 방법
interface PoemWithRhymes {
name: string;
rhymes: boolean;
type: 'rhymes';
}
interface PoemWithPage {
name: string;
pages: number;
type: 'pages';
}
type IPoem = PoemWithPage | PoemWithRhymes;
const poem: IPoem = Math.random() > 0.5
? {name: 'a', pages: 7, type:'pages'} // type 추가
: {name: 'b', rhymes: true, type: 'rhymes'}; // type 추가
if (poem.type === 'pages'){ // 판별된 유니온을 활용한 type narrowing
poem.pages;
}else{
poem.rhymes;
}
& 교차 타입기존 객체 타입을 별칭 객체 타입으로 결합해 새로운 타입 생성 합집합
type Common = {
name: string,
age: number,
gender: string
}
type Animal = {
howl: string
}
type Cat = Common & Animal;
type Dog = Common | Animal;
let dog: Dog = {
howl: 'dogggg'
}
let cat: Cat = {
age: 3,
gender: 'C',
name: 'CC',
howl: 'cattttt'
}
교차 타입은 유니언 타입과 결합할 수 있다.
type Name = { id: number } & ({ first: string } | { last: string })
const name: Name = {
id: 1,
first: 'a'
}
never
교차 타입은 불가능한 타입을 생성해 낼 수도 있음
never로 간주된다.type notPossible = number & string;대부분의 타입스크립트 프로젝트는 never 타입을 거의 사용하지 않지만, 코드에서 불가능한 상태를 나타내기 위해 등장하며 대부분 교차 타입을 잘못 사용해 발생한 실수인 경우가 많음.