[TypeScript 독학] #3 인터페이스

안광의·2022년 2월 17일
2

TypeScript 독학

목록 보기
3/12
post-thumbnail

시작하며

오늘은 타입스크립트에서 객체내의 프로퍼티를 정의하는 방법인 인터페이스에 대해서 작성하려고 한다. 변수를 선언할 때마다 각각 타입을 지정하는 방법보다 인터페이스를 사용하는 방법이 편리하고 재사용도 가능하기 때문에 자주 사용하는 문법이다.

인터페이스란?

인터페이스는 상호 간에 정의한 약속 혹은 규칙을 의미하며 다음과 같은 범주에 대해 약속을 정의할 수 있다.

  • 객체의 스펙(속성과 속성의 타입)
  • 함수의 파라미터
  • 함수의 스펙(파라미터, 반환 타입 등)
  • 배열과 객체를 접근하는 방식
  • 클래스

예제)

interface PersonAge {
  age: number;
}

function logAge(obj: PersonAge) {
  console.log(obj.age);
}
let person = { name: 'Capt', age: 28 };
logAge(person);

personAge라는 인터페이스에 age라는 속성을 추가해 주고 personAge로 타입 지정을 해주면 해당 요소는 number 타입의 age란 속성을 가지고 있는 객체여야 한다. 위 예시처럼 선언된 속성만 가지고 있다면 인터페이스의 속성 개수나 순서는 상관없다.

대개 인터페이스의 첫글자는 대문자로 작성한다.

옵션 속성

interface 인터페이스_이름 {
  속성?: 타입;
}
interface CraftBeer {
  name: string;
  hop?: number;  
}
let myBeer = {
  name: 'Saporo'
};
function brewBeer(beer: CraftBeer) {
  console.log(beer.name); // Saporo
}
brewBeer(myBeer);

함수 파라미터에 옵션 속성을 지정할 수 있는 것처럼 인터페이스도 마찬가지로 ?를 사용하여 옵션 속성을 지정할 수 있다. hop속성은 옵션이기 때문에 hop 속성을 가지고 있지 않은 myBeer 객체를 인자로 전달하여도 에러가 발생하지 않는다.

읽기 전용 속성

interface PersonAge {
  readonly age: number;
}
let james : PersonAge = { age : 26 }
james.age = 27  //error, Cannot assign to 'age' because it is a read-only property.

읽기 전용 속성은 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성을 의미하고 다음과 같이 readonly 속성을 앞에 붙이면 사용할 수 있다.

읽기 전용 배열

추가로 배열을 선언할 때 ReadonlyArray 타입을 사용하면 읽기 전용 배열을 생성할 수 있다.

let arr: ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error, Property 'splice' does not exist on type 'readonly number[]'. Did you mean 'slice'?
arr.push(4); // error, Property 'push' does not exist on type 'readonly number[]'.
arr[0] = 100; // error, Index signature in type 'readonly number[]' only permits reading.

에러 코드를 보니 일반 배열에 사용되는 splice나 push와 같이 배열의 요소를 수정할 수 있는 메소드가 존재하지 않는 것을 확인할 수 있다.

객체 선언과 관련된 타입 체킹

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'?

타입스크립트는 인터페이스를 이용하여 객체를 선언할 때 좀 더 엄밀한 속성 검사를 진행하는데 위 코드를 보면 CraftBeer 인터페이스에는 brand라고 선언되어 있지만 brewBeer() 함수에 인자로 넘기는 myBeer 객체에는 brandon이 선언되어 있어 오탈자 점검을 요하는 오류가 난다.

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

이런 추론을 무시하고 싶을 때는 as를 사용하여 선언하거나

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

CraftBeer 인터페이스를 위와 같이 다른 속성을 정의할 수 있도록 설정해주어야 한다.

함수타입

interface login {
  (username: string, password: string): boolean;
}
let loginUser: login;
loginUser = function(id: string, pw: string) {
  console.log(id, pw);
  return true;
}

인터페이스는 함수의 타입을 정의할 때에도 사용할 수 있다.

클래스 타입

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

클래스 타입도 implements를 사용하여 인터페이스로 타입을 정의할 수 있다.

인터페이스 확장

interface Person {
  name: string;
}
interface Drinker {
  drink: string;
}
interface Developer extends Person, Drinker {
  skill: string;
}
let fe = {} as Developer;
fe.name = 'josh';
fe.skill = 'TypeScript';
fe.drink = 'Beer';

클래스와 마찬가지로 인터페이스도 extends를 사용하여 다른 인터페이스를 상속받아 사용할 수 있다.

하이브리드 타입

interface CraftBeer {
  (beer: string): string;
  brand: string;
  brew(): void;
}
function myBeer(): CraftBeer {
  let my = (function(beer: string) {}) as CraftBeer;
  my.brand = 'Beer Kitchen';
  my.brew = function() {};
  return my;
}
let brewedBeer = myBeer();
brewedBeer('My First Beer');
brewedBeer.brand = 'Pangyo Craft';
brewedBeer.brew();

타입스크립트는 신기하게 여러 가지 타입을 조합해서 만들 수 있는 타입이 존재한다. brewedBeer는 인자를 전달하면 함수가 실행되고 객체처럼 내부의 프로퍼티나 메소드를 불러올 수 있다.

마치며

타입스크립트를 차례대로 배우기 전에 기존에 자바스크립트 파일로 구현된 React 기반 프로젝트를 타입스크립트로 바꾸려고 시도한 적이 있는데, 그때 인터페이스라는 개념이 처음 등장해서 당황했었다. 타입을 지정하는 부분은 대략 이해가 갔는데 인터페이스는 실제 개념을 찾아보지 않고는 사용하는 이유와 목적을 유추하기 어려웠는데 차근차근 학습해보니 편리하게 사용할 수 있는 문법이었다.

profile
개발자로 성장하기

0개의 댓글