what i learned/TIL

[TIL] 2022/03/06 Sun - 타입스크립트 인터페이스

zubetcha 2022. 3. 6. 22:36

 

 

타입스크립트 인터페이스

 

인터페이스는 상호 간 정의한 약속 혹은 규칙을 의미한다. 타입스크립트에서의 인터페이스는 일반적으로 아래와 같은 내용에 대한 약속을 정의할 수 있다.

 

◦ 객체의 스펙 (속성 및 속성 타입)

◦ 함수의 파라미터

◦ 함수의 스펙 (파라미터, 반환값 타입 등)

◦ 배열과 객체를 접근하는 방식

◦ 클래스 

 

let person = { name: 'zubetcha', age: 29 };

function logAge(obj: { age: number }) {
  console.log(obj.age); // 29
}
logAge(person); // 29

 

위의 예시에서 logAge() 함수는 age라는 속성을 가지고 있는 객체를 파라미터로 받는다. 이렇게 파라미터를 받을 때에는 단순히 파라미터의 타입뿐만 아니라 파라미터로 받는 객체의 속성 타입까지도 정의할 수 있다. 여기에 인터페이스를 적용하면 아래와 같이 조금 더 명시적인 모습이 된다.

 

interface personAge {
  age: number;
}

function logAge(obj: personAge) {
  console.log(obj.age);
}
let person = { name: 'zubetcha', age: 29 };
logAge(person);

 

logAge() 함수의 파라미터는 personAge라는 타입을 가져야 한다. 이 때 인터페이스의 속성의 갯수와 파라미터로 받는 객체의 속성의 갯수는 일치하지 않아도 된다. 즉, 인터페이스에 정의된 속성과 타입의 조건만 만족한다면 파라미터로 받는 객체의 속성의 갯수가 더 많아도 상관이 없다. 또한, 인터페이스에 선언된 속성의 순서를 지키지 않아도 된다.

 

옵션 속성

인터페이스를 사용할 때 인터페이스에 정의되어 있는 속성을 반드시 모두 사용하지 않아도 되는데, 이를 옵션 속성이라고 한다. 사용 방법은 속성의 끝에 ?(물음표)를 붙이는 것이다.

 

구문

interface 인터페이스_이름 {
  속성?: 타입;
}

 

예시

brewBeer() 함수에서 파라미터의 타입을 CraftBeer 인터페이스로 선언했음에도 불구하고, 파라미터로 넘긴 객체인 myBeer에는 hop 속성이 없다. 왜냐하면 hop을 옵션 속성으로 선언했기 때문이다.

interface CraftBeer {
  name: string;
  hop?: number;  
}

let myBeer = {
  name: 'Saporo'
};
function brewBeer(beer: CraftBeer) {
  console.log(beer.name); // Saporo
}
brewBeer(myBeer);

 

옵션 속성의 장점

옵션 속성을 사용하면 인터페이스를 사용할 때 속성을 선택적으로 적용할 수 있다는 것 뿐만 아니라 인터페이스에 정의되어 있지 않은 속성에 대해서 인지시켜줄 수 있다는 장점이 있다. 아래와 같이 정의되어 있지 않은 속성에 대해서 에러를 표시할 뿐만 아니라 오탈자가 발생했을 때도 에러를 표시한다.

 

interface CraftBeer {
  name: string;
  hop?: number;
}

let myBeer = {
  name: 'Saporo'
};
function brewBeer(beer: CraftBeer) {
  console.log(beer.brewery); // Error: Property 'brewery' does not exist on type 'Beer'
}
brewBeer(myBeer);
interface CraftBeer {
  name: string;
  hop?: number;
}

function brewBeer(beer: CraftBeer) {
  console.log(beer.nam); // Error: Property 'nam' does not exist on type 'Beer'
}

 

읽기 전용 속성

읽기 전용 속성은 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성을 의미한다. 문법은 아래와 같이 readonly 속성을 앞에 붙이면 된다. 읽기 전용 속성을 사용하면 인터페이스로 객체를 선언한 후 수정하려고 하면 에러를 발생시킨다.

 

구문

interface CraftBeer {
  readonly brand: string;
}

let myBeer: CraftBeer = {
  brand: 'Belgian Monk'
};
myBeer.brand = 'Korean Carpenter'; // error!

 

읽기 전용 배열

읽기 전용 배열을 생성하려면 ReadonlyArray<T> 타입을 사용하면 된다. ReadyonlyArray로 선언한 뒤에는 배열의 내용을 변경할 수 없으며 선언하는 시점에만 값을 정의할 수 있다.

 

let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error

 

타입 체크

인터페이스를 사용하면 객체를 선언할 때 조금 더 엄격한 속성 검사를 할 수 있다. 아래의 코드를 보면 CraftBeer 인터페이스에는 속성 이름이 brand라고 선언되어 있지만 brewBeer() 함수에서 파라미터로 넘기는 객체에는 속성이 brandon으로 선언되어 있어 오탈자 점검을 요하는 에러가 발생한다.

 

interface CraftBeer {
  brand?: string;
}

function brewBeer(beer: CraftBeer) {
  // ..
}
brewBeer({ brandon: 'what' }); // error: Object literal may only specify known properties, but 'brandon' does not exist in type 'CraftBeer'. Did you mean to write 'brand'?

 

위와 같은 타입 추론을 무시하고 싶다면 아래와 같이 인터페이스를 as 키워드를 이용하여 넘겨주면 된다.

 

let myBeer = { brandon: 'what' }';
brewBeer(myBeer as CraftBeer);

 

만약 인터페이스에 속성 이름을 미리 정의하기 어렵거나 인터페이스에 정의되지 않은 속성들을 추가로 사용하고 싶을 때는 아래와 같이 [propName: string]: any; 의 형태로 인터페이스를 선언하면 된다.

 

interface CraftBeer {
  brand?: string;
  [propName: string]: any;
}

 

함수 타입

인터페이스는 함수의 타입을 정의할 때에도 사용할 수 있다. 넘겨 받을 파라미터의 타입 반환 값의 타입을 정의할 수 있다.

 

interface login {
  (username: string, password: string): boolean;
}

let loginUser: login;
loginUser = function(id: string, pw: string) {
  console.log('로그인 했습니다');
  return true;
}

 

클래스 타입

클래스 또한 일정 조건을 만족하도록 타입 규칙을 정할 수 있다.

 

interface CraftBeer {
  beerName: string;
  nameBeer(beer: string): void;
}

class myBeer implements CraftBeer {
  beerName: string = 'Baby Guinness';
  nameBeer(b: string) {
    this.beerName = b;
  }
  constructor() {}
}

 

인터페이스 확장 및 상속

클래스와 마찬가지로 인터페이스도 extends 키워드를 이용하여 인터페이스 간 확장이 가능하며, 여러 인터페이스를 상속 받아서 사용할 수도 있다.

 

// extends 키워드를 이용한 인터페이스 확장
interface Person {
  name: string;
}
interface Developer extends Person {
  skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';


// 인터페이스 상속
interface Person {
  name: string;
}
interface Drinker extends Person {
  drink: string;
}
interface Developer extends Drinker {
  skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';
fe.drink = 'Beer';