[TypeScript] CLASSES AND INTERFACES #4.2 - #4.4

uxolrv·2022년 12월 19일
1

nomadCoder - TypeScript

목록 보기
7/9
post-thumbnail

📌 Classes

💡 readonly

class Dict {
    private words: Words
    constructor() {
        this.words = {}
    }
    // 단어를 추가
    add(word: Word) {
        if (this.words[word.term] === undefined) {
            this.words[word.term] = word.def
        }
    }
}


class Word {
    constructor(
        public term: string,
        public def: string
    ) {}
}

const kimchi = new Word("kimchi", "한국의 음식")

class Dict의 메소드 add에서 단어를 추가하기 위해서는 termdefpublic이어야 한다.



kimchi.def = "xxx"

그러나 이 경우, 위처럼 인스턴스에서 수정이 가능하다.



class Word {
    constructor(
        public readonly term: string,
        public readonly def: string
    ) {}
}

public property의 직접적인 수정이 불가능하도록 하려면 readonly 속성을 부여하면 된다.








💡 static

클래스를 통해 인스턴스를 생성할 필요 없이, 클래스의 속성 또는 메서드를 사용하고자 할 때 사용하는 키워드

class Dict {
    private words: Words
    constructor() {
        this.words = {}
    }
    // 단어를 추가
    add(word: Word) {
        if (this.words[word.term] === undefined) {
            this.words[word.term] = word.def
        }
    }
    static hello() {
      return "hello"
    }
}

Dict.hello()

인스턴스를 생성하지 않고, 클래스에서 바로 호출이 가능하다.



static은 JavaScript의 키워드이기 때문에 컴파일 후에도 남아있게 된다.










📌 Interfaces

어떠한 객체나 클래스를 생성 했을 때 가져야 할 속성 또는 메서드를 정의할 수 있다.

interfaceabstract class와는 달리 JavaScript와 같은 동적 타입 언어 환경에서는 다뤄지지 않기 때문에, 인터페이스를 사용하여 파일 크기를 줄일 수 있다.


💡 typeinterface의 차이


✅ 사용 목적의 차이

🔎 type는?

  • 객체의 스펙(속성 및 타입)을 정의할 수 있다.
  • 타입 alias를 만들 수 있다.
  • 타입이 특정 값을 가지도록 제한할 수 있다.

// 타입이 특정 값을 가지도록 제한
type Team = "red" | "blue" | "yellow"

// 타입 alias 
type Health = number;

// 객체의 스펙 정의
type Player = {
  nickname: string,
  team: Team
  health: Health // 타입 alias
}

const nico: Player = {
  nickname: "nico",
  team: "yellow",
  health: 10
}

type은 string, number 등의 concrete 타입 뿐만 아니라, 특정 값으로 제한할 수도 있다.

위 예시의 경우, Teamred, blue, yellow 라는 특정 string으로 타입을 제한하였다.




🔎 Interface는?

인터페이스는 오직 객체의 스펙을 정의하는 데에만 사용할 수 있다.


type Team = "red" | "blue" | "yellow"
type Health = 1 | 5 | 10

interface Player {
  nickname: string,
  team: Team
  health: Health
}

❗️ interface를 정의할 때에는 =을 사용하지 않는다.



interface Hello = string // Err! 

인터페이스는 객체의 스펙을 정의하는 목적으로만 사용 가능하다.








✅ 확장 가능 여부의 차이

interface User {
  name: string
}

interface Player extends User {
}

const nico: Player = {
  name: "nico"
}

class를 상속하는 class가 있듯, 인터페이스 또한 extends 키워드를 사용해 인터페이스를 확장할 수 있다.

⇒ 문법 구조가 객체지향 프로그래밍과 유사하다!



🔎 동일한 기능을 하는 코드 type과 interface로 살펴보기

interfaceextends 키워드를 사용하여 확장이 가능한 반면, type은 확장이 불가능하기 때문에 & 연산자를 사용해야 한다.

type과 interface의 가장 큰 차이점은 타입의 확장 가능 여부!

✨ 따라서, 객체의 스펙을 정의하는 용도로 사용할 때에는 가급적 확장이 가능한 인터페이스로 선언하는 것이 좋다.








✅ 선언 병합 가능 여부

interface는 선언을 병합(축적)할 수 있으나, type은 선언이 중복되면 에러가 발생한다.

interface User {
  name: string
}

interface User {
  lastName: string
}

interface User {
  health: number
}

위처럼 같은 인터페이스를 반복 선언하여 다른 property를 축적할 수 있다.


type User {
  name: string
}

type User { // Err: Duplicate identifier 'User'
  lastName: string
}

type에서는 위처럼 에러가 발생한다.










💡 abstract class 대신 interface 사용하기

🔎 추상 클래스, 추상 메소드 복습

abstract class User {
  constructor(
    protected firstName: string,
    protected lastName: string
  )
  abstract sayHi(name: string): string
  abstract fullName(): string
}

abstract class(추상 클래스)는 다른 클래스가 따라야 할 청사진을 제시한다.
또한 추상 클래스로는 인스턴스를 만들 수 없다.



class Player extends User {
  fullName() {
    return `${this.firstName} ${this.lastName}`
  }
  sayHi(name: string) {
    return `Hello ${name}. My name is ${this.fullName()}`
  }
}

User를 상속받는 Player에서는 sayHi 메소드와 fullName 메소드를 반드시 구현해야한다.



이렇게 만든 추상 클래스(abstract)는 JavaScript로 컴파일 시 결국 일반 class로 컴파일된다.

그렇다면 추상 클래스는 왜 사용하는 걸까?
⇒ 다른 class들이 표준화된 형태(property, 메소드)를 갖도록 해주는 청사진을 만들기 위해 사용!


그러나! 우리는 Player의 인스턴스를 만들어 사용할 뿐, 사실 상 JavaScript에서 User는 사용되지 않기 때문에, JavaScript로 컴파일 될 필요없는 부분이다.

⇒ ✨ 이때가 바로 인터페이스를 사용해야 할 때!!

인터페이스는 컴파일시 JavaScript로 변환되지 않고 사라지기 때문에, 추상 클래스보다 가벼운 코드를 작성할 수 있다.




🔎 그렇다면 인터페이스를 상속받는 class가 특정 형태를 따르도록 하기 위해서는 어떻게 해야 할까?

implements 키워드를 사용한다!

implements 키워드를 사용하여 확장할 시, 클래스가 인터페이스에 정의된 실행 규칙을 따라야 한다.


interface User {
  firstName: string,
  lastName: string,
  sayHi(name: string): string
  fullName(): string
}

class Player implements User {
  // Err!
  // Class 'Player' incorrectly implements interface 'User'.
  // Type 'Player' is missing the following properties from type 'User': firstName, lastName, sayHi, fullName
}

인터페이스 User의 property를 따르라는 에러가 발생하게 된다.

이처럼 implements 키워드를 사용하면 class가 인터페이스의 형태를 따르게 할 수 있을 뿐만 아니라, JavaScript로 컴파일되지 않기 때문에 코드가 더욱 가벼워진다.



type User = {
  firstName: string,
  lastName: string,
  sayHi(name: string): string
  fullName(): string
}

class Player implements User {
  ...
}

type 또한 implements 키워드를 사용하여 class가 type을 상속받을 수 있다.



class Player implements User {
  constructor(
   	private firstName: string, // Err !!
    public lastName: string
  ){}
  fullName() {
    return `${this.firstName} ${this.lastName}`
  }
  sayHi(name: string) {
    return `Hello ${name}. My name is ${this.fullName()}`
  }
}

단, 인터페이스를 상속할 때에는 property를 private, protected로 만들 수 없다.



interface User {
  firstName: string,
  lastName: string,
  sayHi(name: string): string
  fullName(): string
}

interface Health {
  health: number
}

class Player implements User, Health {
  constructor(
   	public firstName: string,
    public lastName: string,
    public health: number
  ){}
  fullName() {
    return `${this.firstName} ${this.lastName}`
  }
  sayHi(name: string) {
    return `Hello ${name}. My name is ${this.fullName()}`
  }
}

위처럼 한 클래스에서 여러 개의 인터페이스를 동시에 상속하는 것도 가능하다.



function makeUser(user: User) {
  return "h1"
}

makeUser({
  firstName: "nico",
  lastName: "las",
  fullName: () => "xx",
  sayHi: (name) => "string"
})

물론, 클래스와 마찬가지로 인터페이스도 타입으로 지정이 가능하다!

위 경우, 인자의 타입으로 인터페이스를 사용하여 객체의 형태를 지정해주었다.



function makeUser(user: User): User {
  return {
    firstName: "nico",
    lastName: "las",
    fullName: () => "xx",
    sayHi: (name) => "string"
  }
}

만약 인터페이스를 return한다면, class를 return하는 것처럼 new User {...}와 같은 방식으로 작성할 필요없이 인터페이스의 형태에 맞는 객체를 반환해주면 된다.

new는 class의 문법!








📌 정리

1️⃣ type
1) 객체의 형태 및 타입을 정의,
2) 타입 alias 생성,
3) 특정된 값의 타입 생성
용도로 사용할 수 있다.


2️⃣ interface객체의 형태 및 타입을 정의하는 용도로 사용할 수 있다.


3️⃣ typetype을 상속하기 위해서는 & 연산자를 사용하여야 한다.
또한, type같은 이름의 타입을 선언하여 property를 병합할 수 없다.


4️⃣ interfaceinterface를 상속하기 위해서는 extends 키워드를 사용하여야 한다. 또한, interface같은 이름의 인터페이스를 선언하여 property를 병합할 수 있다.

✨ 3️⃣, 4️⃣번이 typeinterface의 가장 큰 차이점!


5️⃣ 인터페이스implements 키워드를 사용하여 class가 특정 메소드나 property를 상속하도록 할 수 있다.


6️⃣ 인터페이스는 추상 클래스와 비슷한 보호 장치의 역할을 하나, JS로 컴파일되지 않기 때문에 추상 클래스를 사용할 때보다 파일 크기를 줄일 수 있다.


7️⃣ typeinterface 모두 class가 상속받을 수 있기 때문에 추상 클래스를 대체할 수 있다.
그러나 class나 객체의 형태를 정의하는 용도로 사용할 때에는 확장성을 고려하여 interface를 사용하는 것이 좋다.










참고

타입스크립트 핸드북
타입스크립트 가이드북
타입스크립트 딥다이브










profile
안녕하세연🙋 프론트엔드 개발자입니다

0개의 댓글