Interface

Happhee·2022년 4월 14일
6

📘 TypeScript

목록 보기
4/10

Interface는 타입 체크를 위해 사용되고, 변수 / 함수 / 클래스에 사용할 수 있다.
ES6는 인터페이스를 지원하지 않지만, TypeScript는 인터페이스를 지원한다.


✨ Interface

인터페이스는 상호 간에 정의한 약속 혹은 규칙을 의미한다.

인터페이스는 프로퍼티와 메소드를 가질 수 있다는 점에서 클래스와 유사하지만, 직접 인스턴스를 생성할 수 없고, 모든 메소드는 추상 메소드이다.

사용 방법

:(colon), ,(comma) 혹은 기호를 사용하지 않을 수 있다.

interface IUser {
  name: string,
  age: number
}
// Or
interface IUser {
  name: string;
  age: number;
}
// Or
interface IUser {
  name: string
  age: number
}

변수

인터페이스는 변수 타입으로 사용할 수 있으며, 인터페이스를 타입으로 선언한 변수는 반드시 해당 인터페이스의 형식을 지켜야 한다.

👇 예제 코드를 살펴보자.

// 인터페이스 Song을 정의
interface Song {
  artist : string;
  title : string;
  isDance : boolean;
}
// 변수 song의 타입으로 Song 인터페이스를 선언
let song : Song;

// 변수 song은 Song 인터페이스를 지켜야 한다.
song = { artist : "IVE", title : "LOVE DIVE", isDance : true };

👇 위의 예제에 추가로 함수에 인터페이스를 사용하여 함수 매개변수의 타입을 선언한 코드이다.

let songs : Song[] = [
  { artist : "IVE", title : "LOVE DIVE", isDance : true }];

function addSong(song: Song){
  songs = [ ...songs, song];
}

const newSong : Song = { artist : "RedVelet", title : "Feel My Rhythm", isDance : true };
addSong(newSong);

console.log(songs);
// [
//  { artist: 'IVE', title: 'LOVE DIVE', isDance: true },
//  { artist: 'RedVelet', title: 'Feel My Rhythm', isDance: true }
//]

이렇게 인터페이스를 사용하게 되면, 함수에 객체를 전달할 때 복잡한 매개변수 체크가 필요하지 않아서 유용하다.

함수

함수의 매개변수 뿐만 아니라 함수의 타입으로도 인터페이스를 사용할 수 있다.

interface IName {
  (PARAMETER: PARAM_TYPE): RETURN_TYPE // Call signature
}

함수의 인터페이스에는 타입이 선언된 파라미터 리스트와 리턴 타입을 정의해야 한다.

// 연습실 크기 함수 인터페이스의 정의
interface PracticeRoomFun {
  (roomWidth: number, roomHeight: number): number;
}

const practiceRoom: PracticeRoomFun = function (width: number, height: number) {
  return width * width * height;
};

console.log(practiceRoom(8, 20)); // 1280

클래스

클래스 뒤에 implements를 작성하고 인터페이스를 선언하면 해당 클래스는 지정된 인터페이스를 반드시 지켜야 한다.

// 노래 인터페이스 정의
interface ISong {
  artist: string;
  title: string;
  isDance: boolean;
}

// Song 클래스는 ISong 인터페이스를 구현하여야 한다.
class Song implements ISong {
  constructor(
    public artist: string,
    public title: string,
    public isDance: boolean
  ) {}
}
// 클래스는 직접 인스턴스 생성 가능
const song = new Song("IVE", "LOVE DIVE", true);

console.log(song);
// Song { artist: 'IVE', title: 'LOVE DIVE', isDance: true }

클래스에 인터페이스를 적용하면 일관성을 유지할 수 있게 되는 장점이 있다.

❗️ 인터페이스 vs 클래스 ❗️

  • 공통점 프로퍼티와 메소드를 가진다.
  • 차이점 직접 인스턴스를 생성할 수 없다.

더불어 인터페이스는 프로퍼티 뿐만 아니라 메소드도 포함할 수 있다.
단, 모든 메소드는 추상 메소드이어야 한다.

// 인터페이스의 정의
interface ISong {
  artist: string;
  title: string;
  isDance: boolean;
  sayDance(): void;
}

class SongMethod implements ISong {
  // 인터페이스에서 정의한 프로퍼티의 구현
  constructor(
    public artist: string,
    public title: string,
    public isDance: boolean
  ) {}

  // 인터페이스에서 정의한 추상 메소드의 구현
  sayDance() {
    console.log(`오늘은 ${this.artist}${this.title}을 출거야 💃`);
  }
}

function danceInfo(song: ISong): void {
  song.sayDance();
}

const todaySong = new SongMethod("IVE", "LOVE DIVE", true);
danceInfo(todaySong);
// 오늘은 IVE의 LOVE DIVE을 출거야 💃

Duck typing

TypeScript는 해당 인터페이스에서 정의한 프로퍼티나 메소드를 가지고 있다면, 다른 프로퍼티의 존재 유무의 관계없이 해당 인터페이스를 구현한 것으로 인정되어 오류 없이 통과된다.

즉, 인터페이스를 구현했다고 해서 모든 타입 체크를 통과하는 것은 아니다.

👇 예제 코드를 살펴보자.

interface IDance {
  artist: string;
  title: string;
}

function 춤추자(dance: IDance): void {
  console.log(`오늘 춤출 곡은 ${dance.artist}${dance.title}입니댜 💃 `);
}

const dance = { artist: "IVE", title: "LOVE DIVE" , isDance : false};
춤추자(dance); 
//오늘 춤출 곡은 IVE의 LOVE DIVE입니댜 💃 

변수 dance는 인터페이스 IDance와 일치하지 않는다.
하지만, artist와 title의 프로퍼티를 가졌다는 이유로 인터페이스라고 간주되어 평가된다.

👇 인터페이스는 자바스크립트의 표준이 아니기 때문에 타입스크립트를 자바스크립트로 바꾸면 인터페이스가 아래와 같이 삭제된다.

function 춤추자(dance) {
    console.log("\uC624\uB298 \uCDA4\uCD9C \uACE1\uC740 ".concat(dance.artist, "\uC758 ").concat(dance.title, "\uC785\uB2C8\uB31C \uD83D\uDC83 "));
}
var dance = { artist: "IVE", title: "LOVE DIVE", isDance: false };
춤추자(dance); //오늘 춤출 곡은 IVE의 LOVE DIVE입니댜 💃

덕 타이핑은 클래스에도 적용된다.

interface ICheer {
  cheer(): void;
}
// 인터페이스 ICheer 구현
class SeoheeCheer implements ICheer {
  cheer() {
    console.log("서히 체고");
  }
}
// 인터페이스 ICheer 구현하지 않음
class ClappingCheer {
  cheer() {
    console.log("Clap, clap, clap, clap, clap clap clap!");
  }
}

function makeCheer(cheer: ICheer): void {
  cheer.cheer();
}
// 에러 없이 처리
makeCheer(new SeoheeCheer());       // 서히 체고
makeCheer(new ClappingCheer());     // Clap, clap, clap, clap, clap clap clap!

SeoheeCheer 에는 ICheer를 구현하였지만, ClappingCheer에는 ICheer를 구현하지 않았다.
하지만, makeCheer 함수 안에 두 가지의 클래스 각각 인스턴스를 전달하여도 에러 없이 처리됨을 확인할 수 있다.


✨ 옵션

인터페이스를 사용할 때 , 인터페이스에 정의되어 있는 속성 모두를 다 사용하지 않아도 된다.

속성 뒤에 ?를 붙여주면 옵션 기능이 활성화 된다.

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

👇 앞선 예제 코드에 옵션 기능을 추가해보자.

interface IDance {
  artist: string;
  title?: string;
}

function 춤추자(dance: IDance): void {
  console.log(`오늘 춤출 곡은 ${dance.artist}${dance.title}입니댜 💃 `);
}

let dancer = { artist: "IVE" };
춤추자(dancer);
//오늘 춤출 곡은 IVE의 undefined입니댜 💃 

옵션으로 지정한 속성에 값을 넣어주지 않으면 undefined가 할당되는 것을 알 수 있다.

🤔 그렇다면, 타입을 중요하게 여기는 타입스크립트에서 왜 옵션 속성을 사용하는 것일까?

장점

속성을 선택적으로 사용할 수 있을 뿐만 아니라 인터페이스에 정의되어 있지 않은 속성에 대해서도 알려줄 수 있는 장점이 있기 때문이다.

function 춤추자(dance: IDance): void {
  console.log(`오늘 춤출 곡은 ${dance.artist}${dance.songTitle}입니댜 💃 `);
  // 'IDance' 형식에 'songTitle' 속성이 없습니다.ts(2339
}

위의 코드처럼 존재하지 않은 속성에 대해서 타입스크립트가 오류를 표시해준다.


✨ 읽기 전용

인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성이다.
readonly 키워드를 속성 앞에 붙여 사용한다.

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

👇 읽기 전용 속성을 추가해보자.

interface IDance {
  readonly artist: string;
  title?: string;
}

// 인터페이스 객체 생성
let dance: IDance = { artist: "IVE" };

// 읽기 전용 속성
dance.artist = "Red Velvet";	//읽기 전용 속성이므로 'artist'에 할당할 수 없습니다.ts(2540)

읽기 전용 속성에 대해 재할당을 시도하면 에러를 가져오게 된다.

배열

읽기 전용 속성을 배열로 작성하고 싶은 경우, ReadonlyArray< T >타입을 사용한다.

👇 한 번 할당된 배열의 내용을 수정하는 것은 불가능하니 주의해서 사용해야 한다.

let favoriteSong: ReadonlyArray<string> = ["icy", "살짝 설렜어", "LOVE DIVE"];

favoriteSong.push("Real Love");
// 'readonly string[]' 형식에 'push' 속성이 없습니다.ts(2339)
favoriteSong[0] = "Feel My Rhythm";
// 'readonly string[]' 형식의 인덱스 시그니처는 읽기만 허용됩니다.ts(2542)

✨ Indexable types

수많은 속성을 가지거나 단언할 수 없는 임의의 속성이 포함되는 구조에서는 기존의 방식만으론 한계가 있다. 이 때, 유용한 방법이 인덱스 시그니처(Index signature)이다.

interface INAME {
  [INDEXER_NAME: INDEXER_TYPE]: RETURN_TYPE // Index signature
}
  • index 배열(객체)에서 위치를 가리키는 숫자(문자)

  • indexing 각 배열 요소(객체 속성)에 접근하기 위하여 인덱스를 사용하는 것

  • element 배열을 구성하는 각각의 값은 배열 요소

👇 예제 코드를 살펴보자.

interface ISong {
  [songIndex: number]: string // Index signature
}
let song: ISong = ["icy", "살짝 설렜어", "LOVE DIVE"]; // Indexable type
console.log(song[0]); // 'icy' is string.
console.log(song[1]); // '살짝 설렜어' is string.
console.log(song['0']); // Error - TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'.

✨ Interface 상속

클래스를 상속받아서 사용하는 것처럼 인터페이스도 extends라는 키워드로 상속 구현이 가능하다.

interface SongInfo{
    artist: string;
    title: string;
}
interface DancerInfo extends SongInfo{
    name: string;
    age: number;
}
interface Group extends DancerInfo{
    people: number;
}

let soptKpop = {} as Group;
soptKpop.people = 20;
// Group
soptKpop.age = 24;
// Group extends DancerInfo 
soptKpop.artist = "IVE";			
soptKpop.title = "LOVE DIVE";
// Group extends DancerInfo extends SongInfo

✨ 하이브리드 타입

자바스크립트의 확장인 타입스크립트 역시 동적인 특성을 가지고 있기에 여러 개의 타입을 조합하여 인터페이스를 구현 할 수 있다.

👇 함수 타입이면서 객체 타입을 정의한 인터페이스 코드이다.

interface IGroup {
  (todayLearned: string): string;
  perfomanceTime: number;
  perfomanceLocation(location: string): void;
}
function myGroup(): IGroup {
  let group = function (todayLearned: string) {
    return todayLearned;
  } as IGroup;
  group.perfomanceTime = 20000;
  group.perfomanceLocation = function (this) {
    console.log(this.perfomanceTime + "시간 공연예정");
  };
  return group;
}
let soptKPop = myGroup();
console.log(soptKPop("LOVE DIVE"));
// LOVE DIVE
soptKPop.perfomanceTime = 30000;
soptKPop.perfomanceLocation("홍대");
// 30000시간 공연예정

🌈 결론

TypeScript에서 Obejct 타입을 선언할 때 interface와 type을 사용한다. 기본적으로 인터페이스로 표현할 수 있는 모든 타입은 인터페이스로 표현하고, 기본 타입에 새로운 이름을 붙이고 싶거나 유니온 타입을 명명하고 싶은 경우 등 인터페이스의 능력 밖인 부분에서만 타입 별칭을 사용해야 한다.


📚 학습할 때, 참고한 자료 📚

profile
즐기면서 정확하게 나아가는 웹프론트엔드 개발자 https://happhee-dev.tistory.com/ 로 이전하였습니다

0개의 댓글