TypeScript - Learning TypeScript Chp 4. Object

이소라·2023년 3월 17일
0

TypeScript

목록 보기
18/28

4. Object

  • 객체 리터럴(Object literal)
    • 각자 타입이 있는 키와 값의 집합

4.1 Object types

  • { ... } 구문을 사용해서 객체 리터럴(object literal)을 생성하면, TypeScript는 해당 속성을 기반으로 새로운 객체 타입 또는 타입 형태를 고려합니다.
    • 해당 객체 타입은 객체의 값과 동일한 속성명과 원시 타입을 갖습니다.
    • 값의 속성에 접근하려면 value.멤버 또는 value['멤버'] 구문을 사용합니다.
    • 값에 존재하지 않는 속성에 접근하려고 하면 타입 오류가 발생합니다.
const poet = {
  born: 1935,
  name: 'Marry Oliver'
}
// (property) born: number
poet['born'];
// (property) name: string
poet.name;

// Error: Property 'end' does not exist on type {born: number; name: string}
poet.end;

4.1.1 Declaring Object Types

  • type annotation을 사용하여 객체 타입을 명시적으로 선언할 수 있습니다.
  • 객체 타입은 객체 리터럴(object literal)과 유사하게 보이지만 필드 값 대신 타입을 사용해 설명합니다.
let poetLater : {
  born: number;
  name: string;
}

poetLater = {
  born: 1935,
  name: 'Mary Oliver'
};

4.1.2 Aliased Object Types

  • 객체 타입을 직접 작성하기보다 type alias를 할당하여 사용하는 방법이 일반적입니다.
type Poet = {
  born: number;
  name: string;
}

let poetLater: Poet;

poetLater = {
  born: 1935,
  name: 'Sara Teasdale'
}



4.2 Structural Typing

  • TypeScript의 타입 시스템은 구조적으로 타입화(strucurally typed)되어 있습니다.
    • 타입을 충족하는 모든 값을 해당 타입의 값으로 사용할 수 있습니다.
    • 매개변수나 변수가 특정 객체 타입으로 선언되면 TypeScript에서 어떤 객체를 사용하든 선언한 객체의 속성이 있어야 합니다.
type WithFirstName = {
  firstName: string;
}

type WithLastName = {
  lastName: string;
}

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

// Ok, 'hasBoth'는 string 타입의 'firstName'을 포함하고 있음
let withFirstName: WithFirstName = hasBoth;
// Ok, 'hasBoth'는 string 타입의 'lastName'을 포함하고 있음
let withLastName: WithLastName = hasBoth;
  • Duck Typing & Structurally Typping
    • JavaScript
      • 덕 타입(Duck typed)
      • 런타입에서 사용될 때까지 객체 타입을 검사하지 않음
    • TypeScript
      • 구조적으로 타입화(Structurally typed)
      • TypeScript의 정적 시스템이 타입을 검사함

4.2.1 Usage Checking

  • type annotation으로 정의된 객체 타입의 변수에 해당 값을 할당할 수 있는지 확인합니다.
    • 할당하는 값에는 객체의 필수 속성이 있어야 합니다.
    • 할당하는 값에 타입에 필요한 멤버가 없다면 타입 오류를 발생합니다.
type FirstAndLastNames = {
  first: string;
  last: string;
}

const hasBoth: FirstAndLastNames = {
  first: 'Sajojini',
  last: 'Naidu'
};
// Error: Property 'last' is missing in type { first: string; }
const hasOnlyOne : FirstAndLastNames = {
  first: 'Sappho'
}
  • 객체 타입은 필수 속성 이름과 해당 속성이 예상되는 타입을 모두 지정합니다.
    • 객체의 속성이 일치하지 않으면 TypeScript는 타입 오류를 발생시킵니다.
type TimeRange = {
  start: Date;
}

const hasStartString : TimeRange = {
  // Error: Type 'string' is not assignable to type 'Date'
  start: '1879-02-13'
}

4.2.2 Excess Property Checking

  • 변수가 객체 타입으로 선언되고, 초깃값에 객체 타입에서 정의된 것보다 많은 필드가 있다면 TypeScript에서 타입 오류를 발생시킵니다.
    • 변수를 객체 타입으로 선언하는 것은 타입 검사가 해당 타입에 예상되는 필드만 있는지 확인하는 방법이기도 합니다.
type Poet = {
  born: number;
  name: string;
}

const poetMatch: Poet = {
  born: 1928,
  name: 'Maya Angelou'
};

const extraProperty: Poet = {
  // Error: Type { activity: string; born: number: name: string; } is not assignable to type 'Poet'
  activity: 'walking',
  born: 1935,
  name: 'Mary Oliver'
}

4.2.3 Nested Object Types

  • JavaScript 객체는 다른 객체의 멤버로 중첩될 수 있습니다.
  • TypeScript의 객체 타입도 타입 시스템에서 중첩된 객체 타입을 나타낼 수 있어야 합니다.
type Poem = {
  author: {
    firstName: string;
    lastName: string;
  }
  name: string;
}

const poemMatch: Poem = {
  author : {
    firstName: 'Sylvia',
    lastName: 'Plath'
  },
  name: 'Lady Lazarus'
};

const PoemMismatch: Poem = {
  author: {
    // Error: Type { name: string } is not assignable to type {firstName: string; lastName: string; }
    name: 'Sylvia Plath',
  }
  name: 'Tulips'
}
  • 중첩된 객체 타입을 type alias로 추출할 수 있습니다.
    • TypeScript의 타입 오류 메세지에 추출된 type alias가 나옵니다.
type Author = {
  firstName: string;
  lastName: string;
}

type Poem = {
  author: Author;
  name: string
}

const PoemMismatch: Poem = {
  author: {
    // Error: Type { name: string } is not assignable to type 'Author'
    name: 'Sylvia Plath',
  }
  name: 'Tulips'
}

4.2.4 Optional Properties

  • 타입의 속성 annotation에서 : 앞에 ?를 추가하면 선택적 속성임을 나타낼 수 있습니다.
    • ?를 사용하여 선택적으로 선언된 속성은 존재하지 않아도 됩니다.
    • ?를 사용하지 않은 속성은 반드시 존재해야 합니다.
type Book = {
  author?: string;
  pages: number
}

const ok: Book = {
  author: 'Rita Dove',
  pages: 80
}

const missing: Book = {
  // Error: Property 'pages' is missing in type { author: string; }
  author: 'Rita Dove'
}



4.3 Unions of Object Types

  • TypeScript에서 변수는 하나 이상의 서로 다른 객체 타입을 가질 수 있습니다.

4.3.1 Infered Object-Type Unions

  • 변수에 여러 객체 타입 중 하나가 될 수 있는 초기값이 주어지면 TypeScript는 해당 타입을 객체 타입 유니언으로 유추합니다.
    • 유니언 타입은 가능한 각 객체 타입을 구성하고 있는 요소를 모두 가질 수 있습니다.
    • 객체 타입에 정의된 각각의 가능한 속성은 비록 초깃값이 없는 선택적(?) 타입이지만 각 객체 타입의 구성 요소로 주어집니다.
const poem = Math.random() > 0.5 
	? {
      name: 'The Double Images',
      pages: 7
    } : {
      name: 'Her Kind',
      rhymes: true
    };
// poem: { 
//	name: string; 
//	pages:number; 
//	rhymes?: undefined;
//} | {
//	name: string;
//	pages?: undefined;
//	rhymes: boolean;
// }
// poem.name: string
// poem.pages: number | undefined
// poem.rhymes: boolean | undefined

4.3.2 Explicit Object-Type Unions

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

type PoemWithRhymes = {
  name: string;
  rhymes: boolean;
}

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem = Math.random() > 0.5 
	? {
      name: 'The Double Images',
      pages: 7
    } : {
      name: 'Her Kind',
      rhymes: true
    };

// Ok
poem.name;
// Error: Property 'pages' does not exist on type 'Poem'
// Property 'pages' does not exist on type 'PoemWithRhymes'
poem.pages;
// Error: Property 'pages' does not exist on type 'Poem'
// Property 'pages' does not exist on type 'PoemWithPages'
poem.rhymes;
  • 잠재적으로 존재하지 않는 객체의 멤버에 대해 접근을 제한하면 코드의 안전을 지킬 수 있습니다.
    • 값이 여러 타입 중 하나일 경우, 모든 타입에 존재하지 않는 속성이 객체에 존재할 거라 보장할 수 없습니다.
    • 객체 유니언 타입에서 모든 타입에 존재하지 않는 속성에 접근하기 위해서 타입을 좁해야 합니다.

4.3.3 Narrowing Object Types

  • 타입 검사기가 유니언 타입 값에 특정 속성이 포함된 경우에만 코드 영역을 실행할 수 있다는 것을 알게 되면, 값의 타입을 해당 속성을 포함한 구성 요소로만 좁힙니다.
if ('pages' in poem) {
  // poem: PoemWithPages
  poem.pages;
} else {
  // poem: PoemWithRhymes
  poem.rhymes;
}
  • TypeScript는 존재하지 않는 객체의 속성에 접근하려고 시도하면 타입 오류를 발생시킵니다.
// Error: Property 'pages' does not exist on type 'PomeWithPages' | 'PoemWithRhymes'
// Property 'pages' does not exist on type 'PoemWtithRhymes'
if (poem.pages) {/* ... */}

4.3.4 Discriminated Unions

  • 판별된 유니언(discriminated union)
    • 객체 속성이 객체의 형태를 나타내도록 하는 객체 타입 유니언
  • 판별값
    • 객체 타입을 가리키는 속성
    • TypeScript는 코드에서 판별값을 사용해 타입을 좁힙니다.
type PoemWithPages = {
  name: string;
  pages: number;
  type: 'pages';
}

type PoemWithRhymes = {
  name: string;
  rhymes: boolean;
  type: 'rhymes';
}

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem = Math.random() > 0.5 
	? {
      name: 'The Double Images',
      pages: 7,
      type: 'pages'
    } : {
      name: 'Her Kind',
      rhymes: true,
      type: 'rhymes'
    };

if (poem.type === 'pages') {
  // poem: PoemWithPages
  console.log(`It's got pages : ${poem.pages}`);
} else {
  // poem: PoemWithRhymes
  console.log(`It's got rhymes : ${poem.rhymes}`);
}



4.4 Intersection Type

  • TypeScript에서 & 교차타입(intersection type)을 사용하여 여러 타입을 동시에 나타냅니다.
  • 교차 타입은 일반적으로 여러 기존 객체 타입을 별칭 객체 타입으로 결합해 새로운 타입을 생성합니다.
type Artwork = {
  genre: string;
  name: string;
}

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

type WrittenArt = ArtWork & Writing;
// WrittenArt: {
//	genre: string;
//	name: string;
//	pages: number;
//}
  • 교차 타입은 유니언 타입과 결합할 수 있으며, 이는 하나의 타입으로 판별된 유니언 타입을 설명하는데 유용합니다.
type ShortPoem = { author: string } & (
	| { kigo: string; type: 'haiku' }
	| { meter: number; type: 'villanelle' }
);

const morningGlory : ShortPoem = {
  author: 'Fukuda Chiyo-ni',
  kigo: 'Morning Glory',
  type: 'haiku'
};

const oneArt: ShortPoem = {
  // Error: Type { author: string; type: 'villanella'; } is not assignable to type 'ShortPoem'
  // Type { author: string; type: 'villanella'; } is not assignable to type { author: string } & 
  // { meter: number; type: 'villanelle' }
);
  // Property 'meter' is missing in type { author: string; type: 'villanella'; }
  author: 'Elizabeth Bishop',
  type: 'villanella'
}

4.4.1 Danger of Intersection Types

  • 교차 타입은 개발자나 컴파일러를 혼동시키는 방식으로 사용하기 쉽습니다.
    • 교차 타입을 사용할 때는 가능한 한 코드를 간결하게 유지해야 합니다.

긴 할당 가능성 오류

  • 복잡한 교차 타입을 만들게 되면 할당 가능성 오류 메세지를 읽기 어려워집니다.
    • 타입이 복잡할수록 타입 검사기의 메세지도 이해하기 더 어려워집니다.
    • TypeScript가 해당 이름을 출력하도록 타입을 별칭 객체 타입으로 분할하면 읽기가 더 쉬워집니다.
type ShortPoemBase = { author: string };
type Haiku = ShortPoemBase & { kigo: string; type: 'haiku' };
type Villanelle = ShortPoemBase & { meter: number, type: 'villanelle' };
type ShortPoem = Haiku | Vinallanelle;

const oneArt : ShortPoem = {
  // Error: Type { author: string; type: 'villanella'; } is not assignable to type 'ShortPoem'
  // Type { author: string; type: 'villanella'; } is not assignable to type 'Vanillanelle'
  // Property 'meter' is missing in type { author: string; type: 'villanella'; } 
  author: 'Elizabeth Bishop',
  type: 'villanella'
}

never

  • 원시 타입의 값은 동시에 여러 타입이 될 수 없기 때문에 교차 타입의 구성 요소로 함께 결합할 수 없습니다.
    • 두 개의 원시 타입의 교차 타입은 never 타입이 됩니다.
// NotPossible: never
type NotPossible = number & string;
  • never 키워드와 never 타입은 프로그래밍 언어에서 bottom 타입 또는 empty 타입을 뜻합니다.
    • bottom 타입은 갑을 가질 수 없고 참조할 수 없는 타입이므로 bottom 타입에 그 어떠한 타입도 제공할 수 없습니다.
// Error: Type 'number' is not assignable to type 'never'
let notNumber: NotPossible = 0;
// Error: Type 'string' is not assignable to type 'never'
let notString: never = '';



참고

  • Learning TypeScript Chapter 4. Object

0개의 댓글