Front-End(Web)/Typescript

[Typescript] 인터페이스(Interface)

ttaeng_99 2021. 2. 22. 19:12
반응형

이전 글에서부터, 타입의 커스터마이징에 대해 얘기하면서, 그리고 Type Aliase 를 공부하면서 Interface 가 종종 거론되었다.

타입의 유형화에 유리한 이 인터페이스에 대해 한번 알아보도록 하자.


💙 Interface 란?

인터페이스는 특정 자료형의 구조를 나타낸 일종의 커스텀 타입이다.

인터페이스는 Javascript같은 동적타입 언어환경에선 다뤄지지 않으나, 정적타입 언어인 Typescript는 타입검사가 요구되므로 이를 지원한다.

 

- Interface 문법 및 적용

'interface' 키워드를 통해 제작할 수 있다. 나머지는, 변수선언 문법과 유사하다.

* Interface 이름 첫 글자는 대문자로 시작하도록 한다. 다른 정적언어는 I+인터페이스명을 권장하나, 타입스크립트는 해당되지 않음

interface [인터페이스명] {
  ...
}

인터페이스는 컴파일된 JS파일에선 확인할 수 없다. 즉, Typescript가 컴파일될 때, 인터페이스를 제외시키는 것이다.

 

interface StudentInfo {
  studentId: number,
  studentName: string,
  age: number,
  subject: string,
  graduated: boolean,
}

function getStudentMoreInfo(studentId: number): StudentInfo {
  return {
    studentId: 10,
    studentName: 'Taeng',
    age: 29,
    subject: 'Typescript',
    graduated: false,
  };
}

인터페이스 적용방법이다. StudentInfo 인터페이스를 제작한 뒤, 이를 getStudentMoreInfo() 함수 반환값의 타입으로 적용한다.

인터페이스는 해당 형태의 타입으로 반환되는 것을 강제한다. (타입 불일치, 필드 누락 등등 시 에러 발생)

 

- Interface vs Type Aliase

두 문법 모두, 기존의 타입들을 활용해 커스텀 타입을 만든다는 공통점이 있다.

앞의 타입에서 언급했듯, 둘의 차이는 Type Aliase는 필드추가 및 타입상속(&로 가능) 이 제한되나, Interface는 가능하다는 것이다.

// Interface
interface ButtonInterface {
  onInit():void;
  onClick():void;
}

interface ButtonInterface {
  onToggle():void;	// ButtonInterface 병함됨
}


// Type Aliase
type ButtonType = {
  onInit():void;
  onClick():void;
}

type ButtonType = {
  onToggle():void;	// 오류: 'ButtonType' 식별자가 중복되었습니다.
}

💙 Interface 추가 문법

1. 인터페이스의 메서드(method) 정의

메서드는 객체 내에서 생성된 함수이다. 역시 마찬가지로, 함수 타입선언 방식을 통해 인터페이스에 포함될 수 있다.

interface CommentBox {
  addComment (comment: string): string;
  addComment: (comment: string) => string;
}

 

2. 선택적 프로퍼티(Optional Properties)

인터페이스에서 반드시 포함되지 않아도 되는 프로퍼티가 있을 것이다. 이를 설정하는 문법으로, '?' 기호를 키 뒤에 붙여주기만 하면 된다.

선택적 프로퍼티의 이점은, 인터페이스에서 반드시 필요하지 않은 프로퍼티를 구분하는 한편, 이에 대한 오류를 확인 가능하단 것이다.

interface StudentInfo {
  studentId: number,
  studentName: string,
  age: number,
  subject?: string,
  graduated: boolean,
}

function studyName(student: StudentInfo) :string {
  return hisSubject: string = student.subjet;		// error! subjet 프로퍼티명 존재하지 않음
}

 

3. 읽기 전용 프로퍼티(Read-only Properties)

말 그대로 수정은 불가하고 읽기만 가능한 필드이다. 생성시 할당된 프로퍼티 값을 수정할 수 없다. 필드명 앞에 readonly 를 붙여준다.

interface StudentInfo {
  readonly studentId: number,
  studentName: string,
  age: number,
  subject?: string,
  graduated: boolean,
}

function modifyStudentInfo (student: StudentInfo): void {
  student.studentId = 40,		// error! (읽기전용)
  student.graduated = true,
}

 

4. 함수 타입 인터페이스

인터페이스는 함수 타입도 정의할 수 있다. 문법은, 중괄호 안에 (매개변수 타입): 반환값 타입; 형태로 만든다.

interface FunctionalInterface {
  (n: number): number;
}

 

함수 타입 인터페이스를 미리 설정하면, 함수 선언시 별도의 타입설정이 불필요해진다.

// Before: 함수타입 설정
const factorial = (n:number): number => {
  if (n === 0) { return 0; }
  if (n === 1) { return 1; }
  return n * factorial(n - 1);
}


// After: 함수타입 인터페이스
interface FactorialInterface {
  (n: number): number;  
}

const facto: FactorialInterface = (n) => {
  if (n === 0) { return 0; }
  if (n === 1) { return 1; }
  return n * facto(n - 1);
};

 

5. 인덱서블 타입 인터페이스

인덱싱이 가능한 자료형을 의미한다. 문법은 대괄호 안에 [인덱스 타입]: 반환값 타입; 형태로 만든다.

이러한 기술법을, 인덱스 시그니쳐(Index Signature) 라고 한다. 인덱스 시그니쳐의 인덱스 타입은 number 혹은 string 이어야만 한다.

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

 

* 이전의 객체 인터페이스에서, 프로퍼티 추가를 위해 작성했던 아래 예제도 인덱스 시그니처에 해당된다.

interface ButtonInterface {
  onInit?():void;
  onClick():void;
  // 인덱스 시그니처
  [prop:string]: any;
}

 

6. 클래스 타입 인터페이스, 인터페이스 확장

인터페이스는 클래스와 비슷하나, 정의만 할 뿐 실제 구현되지는 않는다. (일종의 추상 클래스)

클래스를 통해서 인스턴스를 생성했을 때, 이것이 가져야 할 속성 또는 메서드를 정의할 수 있는 것이다.

또한, 클래스의 extends 키워드로 하나의 클래스에 상속, implements 키워드를 통해 해당 인터페이스들을 클래스와 연결할 수 있다.

// 클래스 => 인터페이스 상속
class Control { 
  private state: any; 
  protected someState: any = 100; 
} 

interface SelectableControl extends Control { 
  select(): void; 
}


// 인터페이스 => 클래스 참조
interface ClockInterface { 
  currentTime: Date; 
  setTime(d: Date): void; 
} 

class Clock implements ClockInterface { 
  currentTime: Date = new Date(); 
  setTime(d: Date) { 
    this.currentTime = d; 
  } 
  constructor(h: number, m: number) { } 
}

 

인터페이스 간에도 마찬가지로, extends(상속)을 통해 다양한 조합을 만들 수 있다.

// 인터페이스 확장
interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}


// 인터페이스 다중 확장(복수)
interface Shape {
  color: string;
}

interface Size {
  width: number;
  height: number;
}

interface DrawSquare extends Shape, Size {}

let square = {} as DrawSquare;
square.color = 'red';
square.width = 20;

 Typescript의 클래스 문법에 대해서는 별도로 포스팅하며, 이전에 Javascript 클래스 공부가 선행되야 할 것 같다.

(Typescript 가이드북 링크 : yamoo9.gitbook.io/typescript/classes )

 

7. 하이브리드 타입

인터페이스는 Javascript의 다양한 타입들을 적용할 수 있으며, 이러한 프로퍼티들을 복합적으로 수행할 수 있다.

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = (function (start: number) { }) as Counter;
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);			// 함수역할 수행 (counter 함수)
c.reset();		// 객체역할 수행: 메서드 프로퍼티
c.interval = 5.0;	// 객체역할 수행: 숫자타입 프로퍼티

사실 Type Aliase와 근본적으로 큰 차이점이 느껴지진 않았다. 좀 더 세부적인 문법들로 구현할 수 있다는 차이점을 제외한다면?

인터페이스 공부도 의미있었지만, 클래스 관련 부분에서 많은 부분을 생략했다. (public, private 개념, 전역 프로퍼티, 추상 클래스 등)

이 부분을 별도로 공부하여 포스팅할 것이며, 이를 위해 Javascript ES6 클래스 자료형에 대한 공부부터 선행되어야 할 것 같다.

 

[출처]

- Typescript 핸드북 : typescript-kr.github.io/pages/basic-types.html#%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8-type-assertions  

- Typescript 가이드북 : yamoo9.gitbook.io/typescript/types/type-assertions  

- kjwsx23 님의 블로그 : kjwsx23.tistory.com/450?category=746259

 

반응형