[Learning Typescript] Ch.4 객체

Typescript
Dragon C's avatar
Nov 29, 2023
[Learning Typescript] Ch.4 객체

1.객체 형태를 설명하는 방법

2. 타입스크립트가 객체의 할당 가능성을 확인하는 방법

4.1 객체 타입

3장 리터럴 타입

구체적인 버전의 원시 타입

const philosopher = "Hypatia";

원시 타입 값 (string, number,…)중 어떤 것이 아닌 특정 원싯값

객체 리터럴

각자의 타입이 있는 키와 값의 집합

객체 타입

  • {…} 구문을 사용하여 객체 리터럴 생성

  • 객체의 값과 동일한 속성명과 원시 타입을 가짐

  • value.멤버 또는 value['멤버'] 로 속성 접근

ex) 다른 속성의 이름으로 접근할 시 오류

const poet = {
	born: 1935,
	name: "Mary Oliver",
};
poet['born']; // 타입: number
poet.name; // 타입: string

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

poet.name = 'mcy'; // Ok

4.1.1 객체 타입 선언

객체의 타입을 명시적으로 선언하기 위해 사용 필드 값 대신 타입을 사용해 설명

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

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

poetLater = "Sappho";
// ~~~~~~
// Error: Type 'string' is not assignable to type '{ born: number; name: string; }'

4.1.2 별칭 객체 타입

  • 같은 객체 타입 중복 작성을 쉽게

  • 오류 메시지를 좀 더 직관적으로 읽기 쉽게

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

let poetLater: Poet;

// Ok
poetLater = {
	born: 1935,
	name: "Sara Teasdale",
};

poetLater = "Emily Dickinson";
//~~~~~~~
// Error: Type 'string' is not assignable to 'Poet'.

💡

대부분의 타입스크립트 프로젝트는 객체 타입을 설명할 때 interface 키워드를 선호 (7장)

4.2 구조적 타이핑

타입시스템

  • 구조적으로 타입화 되어 있음

  • 타입을 충족하는 모든 값을 해당 타입의 값으로 사용 가능

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;
console.log('withLastName: ', withLastName);
// withFirstName: {
//  firstName: "Lucille",
//  lastName: "Clifton"
// }

💡

구조적 타이핑과 덕 타이핑 차이 - 타입스크립트 (구조적 타이핑) : 정적 시스템이 타입을 검사 - 자바스크립트 (덕 타이핑) : 동적 타이핑의 한 종류. 런타임에서 사용될 때까지 객체 타입을 검사하지 않음 * 덕타이핑: 오리처럼 보이고 오리처럼 꽥꽥거리면, 오리일 것이다.

4.2.1 사용 검사

  • 값을 해당 객체 타입에 할당할 수 있는지 확인

  • ex) 객체 타입에 필요한 멤버가 없는 경우

    type FirstAndLastNames = {
    	first: string;
    	last: string;
    };
    
    // 0k
    const hasBoth: FirstAndLastNames = {
    	first: "Sarojini",
    	last: "Naidu",
    };
    
    const hasOnlyOne: FirstAndLastNames = {
    	//~~~~~~~~~~
    	// Error: Property 'last' is missing in type '{ first: string; }'
    	// but required in type 'FirstAndLastNames'.
    	first: "Sappho"
    };

  • ex) 타입이 일치하지 않는 경우

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

4.2.2 초과 속성 검사

  • 타입검사기가 해당 타입에 예상되는 필드만 있는지 확인

  • ex) 객체 타입에서 정의된 것보다 많은 필드가 있는 경우

    type Poet = {
    	born: number;
    	name: string;
    }
    
    // Ok: Poet 의 필드와 일치함
    const poetMatch: Poet {
    	born: 1928,
    	name: "Maya Angelou"
    };
    
    const extraProperty: Poet = {
    	activity: "walking",
    	//~~~~~~~~~~~~~
    	// Error: Type '{activity: string; born: number; name: string; }'
    	// is not assignable to type 'Poet'.
    	// Object literal may only specify known properties,
    	// and 'activity' does not exist in type 'Poet'.
    	born: 1935,
    	name: "Mary Oliver",
    };
  • 초과 속성 검사는 객체 타입으로 선언된 위치에서 생성되는 객체 리터럴에 대해서만 일어남

    const existingObject = {
    	activity: "walking",
    	born: 1935,
    	name: "Mary Oliver",
    };
    
    const extraPropertyButOk: Poet = existingObject; // Ok

4.2.3 중첩된 객체 타입

  • 객체 내 다른 객체의 멤버로 중첩 가능

  • 위에서 설명한 객체 타입에 대한 동일한 규칙 적용

    type Poem = {
    	author: {
    		firstName: string;
    		lastName: string;	
    	};
    	name: string;
    };
    
    // Ok
    const poemMatch: Poem = {
    	author: {
    		firstName: "Sylvia",
    		lastName: "Plath",
    	},
    	name: "Lady Lazarus",
    };
    
    const poemMismatch: Poem = {
    	author: {
    		name: "Sylvia Plath",
    		// ~~~~~~~~~~~~~~~~~~~
    		// Error: Type '{ name: string; }' is not assignable
    		// to type '{ firstName: string; lastName: string; }'.
    		// Object literal may only specify known properties, and 'name'
    		// does not exist in type '{ firstName: string; lastName: string; }'.
    	},
    	name: "Tulips",
    };

4.2.4 선택적 속성

: 앞에 ? 를 추가하여 선택적 속성 정의 가능

type Book = {
	author?: string;
	pages: number;
};

// 0k
const ok: Book = {
	author: "Rita Dove",
	pages: 80,
};

const missing: Book = {
	// ~~~~~~~
	// Error: Property 'pages' is missing in type
	// '{ pages: number; }' but required in type 'Book'.
	author: "Rita Dove",
};
  • 선택적 속성(?): 있거나 없어도 됨

  • 필수 선언된 속성 | undefined : undefined 일지라도 반드시 있어야함

type Writers = {
	author: string | undefined;
	editor?: string;
};

// Ok: author는 undefined으로 제공됨
const hasRequired: Writers = {
	author: undefined,
};

const missingRequired: Writers = {};
// ~~~~~~~~~~~~~~~~~~
// Error: Property 'author' is missing in type '{}' but required in type 'Writers'.

4.3 객체 타입 유니언

  • 하나 이상의 서로 다른 객체 타입을 정의하려고 할 때

  • 속성값을 기반으로 해당 객체 타입 간에 타입을 좁혀야 할 때

4.3.1 유추된 객체 타입 유니언

  • 여러 객체 타입 중 하나가 될 수 있는 초깃값이 주어질 때 객체 타입 유니언으로 유추

  • ex) name 은 필수 / pages 와 rhymes 는 있을 수도, 없을 수도 있음

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

// 타입:
// {
//    name: string;
//    pages: number;
//    rhymes?: undefined;
// }
// |
// {
//    name: string;
//    pages?: undefined;
//    rhymes: boolean;
// }
poem.name;   // string
poem.pages;  // number | undefined
poem.rhymes; // booleans | undefined

4.3.2 명시된 객체 타입 유니언

  • 단점

    • 코드를 더 상세하게 작성

  • 장점

    • 객체 타입을 더 많이 제어 가능

    • ex) name 은 필수 / pages 와 rhymes 는 항상 존재한다는 보장 없음

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 Image", pages: 7 } 
	: { name: "Her Kind", rhymes: true };

poem.name; // Ok

poem.pages;
// ~~~~~
// Error: Property 'pages' does not exist on type 'Poem'.
// Property 'pages' does not exist on type 'PoemWithRhymes'.

poem.rhymes;
// ~~~~~
// Error: Property 'rhymes' does not exist on type 'Poem'.
// Property 'rhymes' does not exist on type 'PoemWithPages'.

4.3.3 객체 타입 내로잉

  • 객체 타입 유니언도 유니언 타입처럼 타입을 좁혀야함.

    if ("pages" in poem) {
    	poem.pages; // Ok: poem은 PoemWithPages로 좁혀짐
    } else {
    	poem.rhymes; // Ok: poem은 PoemWithRhymes로 좁혀짐
    }
  • 존재하지 않는 객체의 속성에 접근하려고 하면 타입 오류 발생함

    if (poem.pages) { /* ... */ }
    // ~~~~~~~~~~~~~~
    // Error: Property 'pages' does not exist on type 'PoemWithPages | PoemWithRhymes'.
    // Property 'pages' does not exist on type 'PoemWithRhymes'.

4.3.3 판별된 유니언 👍🏻

객체의 속성이 객체의 형태를 나타내도록 하는 타입 형태

  • 판별값 - 객체의 타입을 가리키는 속성

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 Image", pages: 7, type: "pages" } 
	: { name: "Her Kind", rhymes: true, type: "rhymes" };
if (poem.type === "pages") {
	console.log('It's got pages: ${poem.pages}'); // Ok
} else {
	console.log('It rhymes: ${poem. rhymes}');
}

poem.type; // 타입: 'pages' ¦ 'rhymes'
poem.pages;
// ~~~~~
// Error: Property 'pages' does not exist on type 'Poem'.
// Property 'pages' does not exist on type 'PoemWithRhymes'.

4.4 교차 타입

  • 교차 타입(&) - 여러 기존 객체 타입을 별칭 객체 타입으로 결합해 새로운 타입을 생성

type Artwork = {
	genre: string;
	name: string;
}

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

type WrittenArt = Artwork & Writing;
// 다음과 같음:
// {
//    genre: string;
//    name: string;
//    pages: number;
// }
  • ex) 교차 타입과 유니언 타입을 결합한 형태

type ShortPoem = { author: string } & (
		¦	{kigo: string; type: "haiku"; }
		¦ { meter: number; type: "villanelle"; }
);

// Ok
const morningGlory: Short Poem = {
	author: "Fukuda Chiyo-ni",
	kigo: "Morning Glory",
	type: "haiku",
};

// 객체 타입에 필요한 멤버가 없음 (사용 검사)
const oneArt: Short Poem = {
	// Error: Type '{ author: string; type: "villanelle"; }'
	// is not assignable to type 'Short Poem'.
	//    Type { author: string; type: "villanelle"; }' is not assignable to 
	//    type { author: string; } & { meter: number; type: "villanelle"; }'. 
	//    Property 'meter' is missing in type '{ author: string; type: "villanelle"; }'
	//    but required in type '{ meter: number; type: "villanelle"; }'. 
	author: "Elizabeth Bishop",
	type: "villanelle",
};

4.4.1 교차 타입의 위험성

긴 할당 가능성 오류

  • 유니언 타입과 결합하는 것처럼 복잡한 교차 타입을 만들수록 오류 메시지를 읽기 어려워짐

  • ex) 별칭 객체 타입 설정

    type ShortPoemBase = { author: string };
    type Haiku = ShortPoemBase & { kigo: string; type: "haiku" };
    type Villanelle = Short PoemBase & { meter: number; type: "villanelle" };
    type ShortPoem = Haiku | Villanelle;
    
    const oneArt: ShortPoem = {
    	// ~~~~~
    	// Error: Type '{ author: string; type: "villanelle"; }'
    	// is not assignable to type/Short Poem'.
    	// Type '{ author: string; type: "villanelle"; }' is not assignable to type 'Villanelle'.
    	// Property 'meter' is missing in type '{ author: string; type: "villanelle"; }'
    	// but required in type '{ meter: number; type: "villanelle"; }'. 
    	author: "Elizabeth Bishop",
    	type: "villanelle",
    };

never

  • 교차 타입은 잘못 사용하기 쉽고 불가능한 타입을 생성함

  • ex) 원시 타입은 동시에 여러 타입이 될 수 없음

type NotPossible = number & string; // 타입: never

never

  • 없는 값의 집합

  • flowbottom type

  • uninhabitable type

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

let notString: NotPossible = "";
// ~~~~~
// Error: Type 'string' is not assignable to type 'never';
  • never 타입이 등장하는 경우는 대부분 교차 타입을 잘못 사용했을 때 발생

Share article

Typescript Study