[Typescript] 인터페이스(Interface)
이전 글에서부터, 타입의 커스터마이징에 대해 얘기하면서, 그리고 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