타입스크립트 스터디에서 정리한 내용!
interface는 자주 사용해서 간단하겠거니~ 생각하고 읽기 시작했는데 내 생각보다 나는 더 모르고 타입의 세계는 넓다는 것을 깨달았다.
챕터7. 인터페이스
러닝 타입스크립트 131p
핵심
우리가 직접 만들 수 있는데
왜 지루한 내장 티입 형태만 사용하나요?
인터페이스? : 연관된 이름으로 객체 형태를 설명하는 또 다른 방법.
특징
- 별칭으로 된 객체 타입과 유사
- 더 읽기 쉬운 오류 메시지
- 더 빠른 컴파일러 성능
- 클래스와의 더 나은 상호 운용성
을 위해 사용된다
📚 07.1 타입 별칭 vs. 인터페이스
// 타입 별칭
type Poet = {
born: number;
name: string;
}
// 인터페이스
interface Poet {
born: number;
name: string;
}
타입 별칭과 인터페이스는 거의 비슷해 보인다. 무슨 차이가 있을까?
타입 별칭과 인터페이스의 차이
- 인터페이스는 속성 증가를 위해 병합할 수 있다. => 내장된 전역 인터페이스 또는 npm 패키지와 같은 외부 코드를 사용할 때 특히 유용하다
- 인터페이스는 클래스가 선언된 구조의 타입을 확인하는 데 사용할 수 있다
- 일반적으로 인터페이스에서 타입스크립트 타입 검사기가 더 빨리 작동한다.
- 인터페이스 : 내부적으로 더 쉽게 캐시할 수 있는 명명된 타입 선언
- 타입 별칭 : 새로운 객체 리터럴의 동적인 복사 붙여넣기
- 인터페이스는 이름 없는 객체 리터럴의 별칭이 아닌 명명된 객체로 간주하므로, 특이케이스에서 나타나는 오류 메시지를 더 쉽게 읽을 수 있다
📚 07.2 속성 타입
자바스크립트 객체를 실제로 사용할 때 어려움을 느낄 수 있다.
타입스크립트 인터페이스는 이런 부분을 모델링할 수 있도록 유용한 타입 시스템 도구를 제공한다
- 선택적 속성
- 읽기 전용 속성
- 함수와 메서드
- 호출 시그니처
- 인덱스 시그니처
- 중첩 인터페이스
1. 선택적 속성
모든 객체가 필수적으로 인터페이스 속성을 가질 필요는 없다
=> 타입 애너테이션 앞에 '?'을 사용해 선택적 속성임을 나타낼 수 있다.
interface Apple {
color?: string;
name: string;
price: number;
}
// 🤔 name : string, price : number, color은 string으로 들어오거나 생략할 수 있구나
const pickedApple = {
name: 'green apple';
price: 300;
} // 👍
2. 읽기 전용 속성
인터페이스에 정의된 객체의 속성을 재할당하지 못하도록 하고 싶다면?
속성 이름 앞에 readonly 키워드를 추가할 수 있다
interface Apple {
color?: string;
name: string;
readonly price: number;
}
// 🤔 price는 재할당할 수 없구나!
function pickingApple(apple:Apple){
console.log(apple.price) // ok
}
function changeApplePrice(apple:Apple){
apple.price += 500 // ERROR!!
}
readonly 제한자
- 타입 시스템에만 존재
- 인터페이스에서만 사용 가능
- 객체의 인터페이스를 선언하는 위치에서 사용 🌟
- 실제 객체에는 적용되지 않는다
🌟 이게 무슨 말일까?
const newApple = {
color: 'red',
name: '맛있는 사과',
price: 2000,
}
newApple.price += 500
// OK : newApple은 유추된 객체 타입이다 === Apple 객체가 아니기 때문에 가능!
changeApplePrice(newApple)
// OK : 유추된 객체 타입이었던 newApple의 더 구체적인 버전의 Apple을 읽는다
🙄 "언제 사용하면 좋을까?"
readonly는 단지 타입스크립트 타입 검사기를 사용해 개발 중에 그 속성이 수정되지 못하도록 보호하는 역할을 한다
3. 함수와 메서드
인터페이스 멤버를 함수로 선언하는 두 가지 방법
- 메서드 구문: 객체의 멤버로 호출되는 함수로 선언
member(): void
- 속성 구문: 독립 함수와 동일하게 선언
member: () => void
두 방법 모두 선택적 속성으로 만들 수 있다
interface OptionalReadonlyFunctions {
a? : () => string; // 속성 구문
b?(): => string; // 메서드 구문
}
메서드와 속성의 차이점
- 메서드는 readonly로 선언할 수 없지만 속성은 가능하다
- 인터페이스 병합은 메서드와 속성을 다르게 처리한다
- 타입에서 수행되는 일부 작업은 메서드와 속성을 다르게 처리한다
두 가지를 혼동하거나 차이점을 이해하지 못한다고 해도 걱정하지 마세요
this 스코프와 선택한 방식을 의도하지 않는 한 코드에 거의 영향을 주지 않습니다
4. 호출 시그니처
인터페이스와 객체 타입은 호출 시그니처로 선언할 수 있다
호출 시그니처란?
- 값을 함수처럼 호출하는 방식에 대한 타입 시스템의 설명이다
- 할당 가능한 매개변수와 반환 타입을 가진 함수
- 함수 타입과 비슷하지만
:
대신=>
로 표시한다
type FunctionAlias = (input: string) => number; 🌟
interface CallSignature {
(input: string): number;
}
🌟🙄 "언제 사용하지?"
호출 시그니처는 사용자 정의 속성을 추가로 갖는 함수를 설명하는 데 사용할 수 있습니다
ex
interface FunctionWithCount {
count : number;
():void
}
let hasCallCount : FunctionWithCount
function keepsTrackOfCalls(){
keepsTrackOfCalls.count += 1;
console.log(keepsTrackOfCalls.count)
}
keepsTrackOfCalls.count = 0;
hasCallCount = keepsTrackOfCalls;
function doseNotHaveCount(){
console.log('no idea')
}
hasCallCount = doseNotHaveCount
5. 인덱스 시그니처
컨테이너 객체의 경우 모든 가능한 키에 대해 필드가 있는 인터페이스를 선언하는 것은 비현실적인거나 불가능합니다
타입스크립트는 인덱스 시그니처 구문을 제공해
- 인터페이스의 객체가 임의의 키를 받고
- 해당 키 아래의 특정 타입을 반환할 수 있음을 나타낸다.
- 인터페이스의 객체는 일반적으로 문자열 키와 함께 사용된다
{[key: string]: ...}
과 같이 사용된다
interface Seed {
[key:string]: string
}
const seeds: Seed = {}
seeds.rose = '장미' // OK
seeds.sunflower = 0 // ERROR!!!!
인덱스 시그니처는 객체에 값을 할당할 때 편리하지만 타입 안정성을 완벽하게 보장하지는 않는다
interface DatesByName {
[i:string]: Date;
}
const publishDates: DatesByName ={
Frankenstein: new Date('1 January 1828')
}
publishDates.Frankenstein // OK
publishDates.Beloved;
// 🤔 Date 타입이겠군
// 🏃♂️ undefined
console.log(publishDates.Beloved.toString())
// 🤔 음~ Ok
// 🏃♂️ ERROR!!!!
키/값 쌍을 저장할 때 키를 미리 알 수 없으면 Map을 사용하는 편이 더 안전하다
속성과 인덱스 시그니처 혼합
interface Apples {
factory: string ✨
[key:string]:{
color?: string
name: string
price: number
}
const newApple :Apples={
factory: '사과공장',
greenApple: {
color: green,
name: '초록사과',
price: 500
}
}
// OK
const newApple2 :Apples={
greenApple: {
color: green,
name: '초록사과',
price: 500
}
}
// ERROR!!! factory type이 missing
}
interface Apples {
factory: '멋쟁이공장' ✨
[key:string]:{
color?: string
name: string
price: number
}
}
=> Apples를 사용하는 모든 객체의 factory는 '멋쟁이공장'이 되어야 한다
속성과 인덱스 시그니처 혼합
객체의 키로 숫자만 허용할 때?
- 명명된 속성은 타입을 포괄적인 용도의 string 인덱스 시그니처의 타입으로 할당해야 한다
interface moreNarrowNumbers {
[i:number] : string
[i:string] : string | undefined
}
// OK
interface moreNarrowNumbers {
[i:string] : string
[i:number] : string | undefined ✨ // ERROR!!!!
}
객체의 키가 숫자일 경우에도 obj.3 혹은 obj[3]으로 접근이 불가능하기 때문에, string 인덱스 시그니처의 타입으로 할당해야 한다!
6. 중첩 인터페이스
inteface fruit {
factory:{
name: string
owner: string
};
setting: Setting;
}
interface Setting {
name: string;
color: string;
price: number;
}
Setting이라는 interface를 interface fruit의 setting 하의 타입으로 중첩시켰다.
복잡한 interface 를 분할할 수 있고 재사용성이 용이해진다
📚 07.3 인터페이스 확장
타입스크립트는 인터페이스가 다른 인터페이스의 모든 멤버를 복사해 선언할 수 있는 확장된 인터페이스를 허용합니다
- 확장할 인터페이스의 이름 뒤에 extends 키워드를 추가해서 표시
- 파생 인터페이스를 준수하는 모든 객체는 기본 인터페이스의 모든 멤버도 가져야 한다
interface Bread {
milk: string;
butter: string;
price: number
}
interface CoffeeBread extends Bread {
coffee: string
}
const coffeeBurn : CoffeeBread ={
milk: '상하목장',
butter: '서울버터',
price: 400,
coffee: '믹스커피',
}
인터페이스 확장은 프로젝트의 한 엔티티 타입이 다른 엔티티의 모든 멤버를 포함하는 상위 집합을 나타내는 실용적인 방법
1. 재정의된 속성
파생 인터페이스는 다른 타입으로 속성을 다시 선언해 기본 인터페이스의 속성을 재정의 하거나 대체할 수 있습니다.
사용 이유
- 유니언 타입의 더 구체적인 하위 집합으로 만들거나
- 기본 인터페이스 타입에서 확장된 타입으로 만들기 위해
interface Cats {
name: string
}
interface MyCats extends Cats {
name: '나비' | '냥냥이' | '야옹이'
}
const myCat :MyCats = {
name:'고양이' // ERROR!!!
}
const myCat :MyCats = {
name:'냥냥이' // 😻 OK
}
interface NewCats extends Cats {
name: number | undefined
}
// ERROR!!!!
// number | undefined는 string에 할당할 수 없음!
2. 다중 인터페이스 확장
interface Bread {
flour: string;
price: number;
}
interface MilkBread {
milk: string;
}
interface ChocoBread {
choco: string;
}
interface StrawberryBread {
strawberry: number;
}
interface Cake {
butter: string;
cream: string;
}
interface MilkChocoStrawberryCake extends Bread, MilkBread, chocoBread, StrawberryBread, Cake
📚 07.4 인터페이스 병합
인터페이스의 특징 중 하나는 서로 병합하는 능력입니다
두 개의 인터페이스가 동일한 이름으로 동일 스코프에 선언된 경우,
선언된 모든 필드를 포함하는 더 큰 인터페이스가 코드에 추가됩니다
interface Merged {
fromFirst: string
}
interface Merged {
fromSecond: number
}
// 🤔
// interface Merged {
// fromFirst: string
// fromSecond: number
// }
// 이구나!
- 일반적으로 인터페이스 병합을 자주 사용하지는 않는다
- 외부 패키지 또는 Window같은 내장된 전역 인터페이스를 보강하는 데 유용하다
interface Window {
myEnvironmentVariable: string;
}
window.myEnvironmentVariable //type : string
- 병합된 인터페이스는 타입이 다른 동일한 이름의 속성을 여러 번 선언할 수 없다
- 이미 인터페이스에 선언되어 있다면 나중에 병합된 인터페이스에서도 동일한 타입을 사용해야 한다
interface MergedProperties {
same: (input: boolean) => string;
different: (input: string) => string;
}
interface MergedProperties {
same: (input: boolean)=> string; // OK
different: (input:number) => string;
// ERROR!!!!!!!!!!!
}
- 병합된 인터페이스는 타입이 다른 동일한 이름의 메서드을 정의할 수 있다(메서드에 대한 함수 오버로드 발생)
interface MergedMethods {
different(input: string) : string;
}
interface MargedMethods {
different(input: number): string ; // OK
}
'✍ 공부 > TypeScript' 카테고리의 다른 글
[type-challenges] Length of Tuple (0) | 2023.05.23 |
---|---|
[type-challenges] First of Array (0) | 2023.05.23 |
[type-challenges] Tuple to Object (0) | 2023.05.16 |
[type-challenges] Readonly (0) | 2023.05.16 |
[type-challenges] Pick (1) | 2023.05.16 |