러닝 타입스크립트 | ch 4. 객체

doodoo·2023년 3월 1일
0
post-thumbnail

객체 타입 선언

문법

  • 객체 타입은 객체 리터럴과 비슷해보이지만, 필드 값 대 신 타입을 사용해 설명한다.
  • 필드에 타입을 지정 후 ,가 아닌 ;으로 구분한다.
let obj: {
	key1: type;
  	key2: type;
}

예시

let poetLater: {
    born: number;
    name: string;
}

poetLater = {
    born: 1935,
    name: "Mary Oliver",
}

별칭 객체 타입

  • 각 객체 타입에 타입 별칭을 할당해 사용할 수 있다(더 일반적인 방법).
  • 객체 타입을 타입 별칭으로 만들면 할당 가능성 오류 메시지를 좀 더 읽기 쉽게 만들 수 있다.

문법

type Name = {
    key1: type;
  	key2: type;
}

예시

type Poet = {
    born: number;
    name: string;
}

let poetLater: Poet;

poetLater = {
    born: 1935,
    name: "Mary Oliver",
}

구조적 타이핑

  • 타입스크립트의 타입 시스템은 구조적으로 타입화 되어있다.
    👉 타입을 충족하는 모든 값을 해당 타입의 값으로 사용 가능하다.
  • 구조적 타이핑은 덕 타이핑과는 다르다.
    • 자바스크립트는 덕 타입이고, 타입스크립트는 구조적으로 타입화 된다.
    • 구조적 타이핑: 정적 시스템이 타입을 검사하는 경우
    • 덕 타이핑: 런타임에서 사용될 때까지 타입을 검사하지 않는 것

예시

type WithFirstName = {
    firstName: string;
}

type WithLastName = {
    lastName: string;
}

const hasBoth = {
    firstName: "Lucille",
    lastName: "Clifton",
}

let withFirstName: WithFirstName = hasBoth;
let withLastName: WithLastName = hasBoth;
  • hasBoth 객체는 firstName, lastName을 가지고 있다. 구조만 보면 WithFirstName 타입과 WithLastName 타입을 포함하고 있다.
  • 그래서 WithFirstName 타입인 변수 withFirstName에 hasBoth 객체를 할당할 수 있다. hasBoth는 string 타입의 firstName을 포함하고 있기 때문

사용 검사

  • 어떤 변수가 객체 타입으로 선언되면, 타입스크립트는 그 변수에 할당하려는 값이 객체 타입에 할당할 수 있는지 확인한다.
    • 할당하려는 값에는 객체 타입의 모든 속성이 있어야 한다.
    • 일치하지 않는 타입도 허용 되지 않는다.

예시

type Color = {
    name: string;
    id: number;
}

const red: Color = {
    name: 'red',
    id: 1,
}
// OK

const blue: Color = {
    name: 'blue',
}
// Error: Property 'id' is missing in type '{ name: string; }' but required in type 'Color'.

blue 변수에 id 속성이 없어서 에러가 발생했다.

type Color = {
    name: string;
    id: number;
}

const pink: Color = {
    name: 'pink',
    id: '2'
}
// Error: Type 'string' is not assignable to type 'number'.

pink 객체의 id 속성이 숫자 타입이 아니라서 에러가 발생했다.


초과 속성 검사

초깃값에 객체 타입에서 정의된 것보다 많은 필드가 있다면 타입 오류가 발생한다.

type Color = {
    name: string;
    id: number;
}

const orange: Color = {
    name: 'orange',
    id: 3,
    hex: '#fc8403',
}
// Error: Type '{ name: string; id: number; hex: string; }' is not assignable to type 'Color'.
//  Object literal may only specify known properties, and 'hex' does not exist in type 'Color'.

hex 속성은 Color 타입에 없기 때문에 에러가 발생한다.


초과 속성 검사는 객체 타입으로 선언된 위치에서 생성되는 객체 리터럴에 대해서만 일어난다. 기존 객체 리터럴을 제공하면 초과 속성 검사를 우회한다.

type Color = {
    name: string;
    id: number;
}

const orange = {
    name: 'orange',
    id: 3,
    hex: '#fc8403',
}

const hexOrange: Color = orange; // OK 

hexOrange 변수는 초깃값이 구조적으로 Color와 일치하기 때문에 타입 오류가 발생하지 않는다.


중첩된 객체 타입

객체 타입은 중첩시킬 수 있다.

예시

type Student = {
    name: {
        firstName: string;
        lastName: string;
    }
    age: number;
}

const student1: Student = {
    name: {
        firstName: 'Sylvia',
        lastName: 'Plath',
    },
    age: 20,
} // OK

const student2: Student = {
    name: {
        firstName: 'Sylvia',
    },
    age: 23,
}
// Error: Property 'lastName' is missing in type '{ firstName: string; }' but required in type '{ firstName: string; lastName: string; }'.

중첩된 객체 타입을 작성할 때 중첩되는 객체 속성의 형태를 별칭 객체 타입으로 추출하는 방법도 있다.
👉 타입 오류 메시지에 더 많은 정보를 담을 수 있다.

예시

type Name = {
    firstName: string;
    lastName: string;
}

type Student = {
    name: Name;
    age: number;
}

const student1: Student = {
    name: {
        firstName: 'Sylvia',
        lastName: 'Plath',
    },
    age: 20,
} // OK

선택적 속성

: 앞에 ? 를 추가하면 선택적 속성임을 나타낼 수 있다.

예시

type Book = {
    pages: number; // 필수 속성(꼭 있어야 함)
    author?: string; // author는 선택적 속성(있어도 되고 없어도 됨)
}

const ok: Book = {
    pages: 378,
    author: 'Rita Dove',
}
// OK

const ok2: Book = {
    pages: 378,
}
// OK

const missing: Book = {
    author: 'Rita Dove',
}
// Property 'pages' is missing in type '{ author: string; }' but required in type 'Book'.

유의할 점

선택적 속성과 undefined를 포함한 유니언 타입의 속성은 다르다.

  • 선택적 속성: 속성이 존재하지 않아도 됨
  • undefined를 포함한 유니언 타입 속성: 값이 undefined여도 반드시 존재해야 함
type Book = {
    pages: number;
    author: string | undefined;
}

const ok: Book = {
    pages: 378,
    author: undefined,
}
// OK

const ok2: Book = {
    pages: 378,
}
// Error: Property 'author' is missing in type '{ pages: number; }' but required in type 'Book'.

객체 타입 유니언

유추된 객체 타입 유니언

변수에 여러 객체 타입 중 하나가 될 수 있는 초깃값이 주어지면 타입스크립트는 해당 타입을 객체 타입 유니언으로 유추한다.

const color = Math.random() > 0.5 
? { name: 'red', hex: '#fc0303' }
: { name: 'blue', id: 2 };

// 타입: 
// {
//     name: string; 
//     hex: string;
//     id?: undefined;
// } | {
//     name: string; 
//     id: number;
//     hex?: undefined;
// }
// name은 필수, hex와 id는 옵션 

명시된 객체 타입 유니언

  • 객체 타입의 조합을 명시하면 더 명확히 정의할 수 있다.
  • 값의 타입이 객체 타입으로 구정된 유니언이라면, 타입 시스템은 모든 유니언 타입에 존재하는 속성에 대한 접근만 허용한다.
  • 모든 타입에 존재하지 않는 속성에 접근하려면 타입을 좁혀야 한다.
type ColorWithHex = {
    name: string;
    hex: string;
}

type ColorWithId = {
    name: string;
    id: number;
}

type Color = ColorWithHex | ColorWithId;

const color: Color = Math.random() > 0.5 
? { name: 'red', hex: '#fc0303' }
: { name: 'blue', id: 2 };

color.name; // OK 

color.id; 
// Error: Property 'id' does not exist on type 'Color'.
// Property 'id' does not exist on type 'ColorWithHex'.

객체 타입 내로잉

in 연산자로 타입을 좁힐 수 있다.

if('id' in color) {
    color.id; // OK: color는 ColorWithId로 좁혀짐 
} else {
    color.hex; // OK: color는 ColorWithHex로 좁혀짐 
}

타입스크립트는 if(color.id) 형태로 참 여부를 확인하는 것을 허용하지 않는다. 존재하지 않는 객체 속성에 접근하려고 시도하면 타입 오류로 간주된다.

판별된 유니언(Discriminated Union)

  • 동일한 속성이지만 각각 다른 값을 할당해서 타입 내로잉을 수행할 수도 있다.
  • 타입 내로잉 없이는 값에 존재하는 속성을 보장할 수 없다.
type ColorWithHex = {
    name: string;
    hex: string;
    type: 'hex';
}

type ColorWithId = {
    name: string;
    id: number;
    type: 'id';
}

type Color = ColorWithHex | ColorWithId;

const color: Color = Math.random() > 0.5 
? { name: 'red', hex: '#fc0303', type: 'hex' }
: { name: 'blue', id: 2, type: 'id' };

color.name; // OK 

if(color.type === 'id') {
    color.id;
}

color.type; // type: "hex" | "id"

교차타입

  • 교차 타입 & 을 사용해 여러 타입을 동시에 나타낸다.
  • 여러 기존 객체 타입을 별칭 객체 타입으로 결합해 새로운 타입을 생성한다.
type ArtWork = {
    genre: string;
    name: string;
}

type Writing = {
    pages: number;
    name: string;
}

type WrittenArt = ArtWork & Writing;

// 다음과 같음:
// {
//     name: string;
//     genre: string;
//     pages: number;
// }
  • 교차 타입은 유니언 타입과 결합할 수 있다.
  • 하나의 타입으로 판별된 유니언 타입을 설명하는데 유용하다.
type ShortPoem = { author: string } & ({ kigo: string; type: 'haiku'; } | { meter: number; type: 'villanelle'; });
// 항상 author 속성을 가짐 
// type 속성으로 판별된 유니언 타입 

교차 타입의 위험성 (1) 긴 할당 가능성 오류

  • 교차 타입을 사용할 때는 가능한 한 코드를 간결하게 유지해야 한다.
  • 복잡할수록 타입 검사기의 메시지도 이해하기 어려워진다.
  • 해결 방법: 타입 분할하기
type ShortPoemBase = { author: string };
type Haiku = ShortPoemBase & { kigo: string; type: 'haiku' };
type Villanelle = ShortPoemBase & { meter: number; type: 'villanelle' };
type ShortPoem = Haiku | Villanelle;

교차 타입의 위험성 (2) never

  • 두 개의 원시 타입을 결합하면 never 타입이 된다.
  • never 키워드와 타입은 프로그래밍 언어에서 bottom 타입 또는 empty 타입을 뜻한다.
    • bottom 타입은 값을 가질 수 없고 참조할 수 없는 타입
type NotPossible = number & string; // type NotPossible = never

let notNumber: NotPossible = 0; // Type 'number' is not assignable to type 'never'.

let notString: NotPossible = ''; // Type 'string' is not assignable to type 'never'.

0개의 댓글