4. Object
4.1 Object types
- { ... } 구문을 사용해서 객체 리터럴(object literal)을 생성하면, TypeScript는 해당 속성을 기반으로 새로운 객체 타입 또는 타입 형태를 고려합니다.
- 해당 객체 타입은 객체의 값과 동일한 속성명과 원시 타입을 갖습니다.
- 값의 속성에 접근하려면
value.멤버
또는 value['멤버']
구문을 사용합니다.
- 값에 존재하지 않는 속성에 접근하려고 하면 타입 오류가 발생합니다.
const poet = {
born: 1935,
name: 'Marry Oliver'
}
poet['born'];
poet.name;
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'
}
let withFirstName: WithFirstName = hasBoth;
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'
};
const hasOnlyOne : FirstAndLastNames = {
first: 'Sappho'
}
- 객체 타입은 필수 속성 이름과 해당 속성이 예상되는 타입을 모두 지정합니다.
- 객체의 속성이 일치하지 않으면 TypeScript는 타입 오류를 발생시킵니다.
type TimeRange = {
start: Date;
}
const hasStartString : TimeRange = {
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 = {
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: {
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: {
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 = {
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
};
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
};
poem.name;
poem.pages;
poem.rhymes;
- 잠재적으로 존재하지 않는 객체의 멤버에 대해 접근을 제한하면 코드의 안전을 지킬 수 있습니다.
- 값이 여러 타입 중 하나일 경우, 모든 타입에 존재하지 않는 속성이 객체에 존재할 거라 보장할 수 없습니다.
- 객체 유니언 타입에서 모든 타입에 존재하지 않는 속성에 접근하기 위해서 타입을 좁해야 합니다.
4.3.3 Narrowing Object Types
- 타입 검사기가 유니언 타입 값에 특정 속성이 포함된 경우에만 코드 영역을 실행할 수 있다는 것을 알게 되면, 값의 타입을 해당 속성을 포함한 구성 요소로만 좁힙니다.
if ('pages' in poem) {
poem.pages;
} else {
poem.rhymes;
}
- TypeScript는 존재하지 않는 객체의 속성에 접근하려고 시도하면 타입 오류를 발생시킵니다.
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') {
console.log(`It's got pages : ${poem.pages}`);
} else {
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;
- 교차 타입은 유니언 타입과 결합할 수 있으며, 이는 하나의 타입으로 판별된 유니언 타입을 설명하는데 유용합니다.
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 = {
);
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 = {
author: 'Elizabeth Bishop',
type: 'villanella'
}
never
- 원시 타입의 값은 동시에 여러 타입이 될 수 없기 때문에 교차 타입의 구성 요소로 함께 결합할 수 없습니다.
- 두 개의 원시 타입의 교차 타입은 never 타입이 됩니다.
type NotPossible = number & string;
- never 키워드와 never 타입은 프로그래밍 언어에서 bottom 타입 또는 empty 타입을 뜻합니다.
- bottom 타입은 갑을 가질 수 없고 참조할 수 없는 타입이므로 bottom 타입에 그 어떠한 타입도 제공할 수 없습니다.
let notNumber: NotPossible = 0;
let notString: never = '';
참고
- Learning TypeScript Chapter 4. Object