본문 바로가기
FRONTEND/Javascript

5) Typescript - Classes

by 또야또야 2021. 2. 8.
반응형

전통 JS는 재사용 가능한 컴포넌트를 만들기 위한 function과 prototype 기반 상속을 사용하였지만, class가 함수를 상속받고 classe로부터 object가 만들어지는 object-oriented에 익숙한 프로그래머들에게는 약간 어색합니다. ECMAScript 2015(ES6)를 시작으로, JS 프로그래머들은 그들의 앱을 이 객체지향(object-oriented) 접근을 사용하여 그들의 앱을 만들 수 있게 되었습니다. TS에서는, JS로 컴파일해서 모든 주요 브라우저와 플랫폼에서 사용할 수 있는 개발자들에게 이 기술을 사용하도록해주었고, JS의 다음버전을 기다릴 필요가 없도록 해주었습니다.

Classes

class Greeter {
  greeting: string;
  constructor (message: string) {
    this.greeting = message
  }

  greet () {
    return `Hello ${this.greeting}`
  }
}

let greeter = new Greeter('world')

간단한 class 기반 예제를 살펴보면, class의 멤버중 하나를 this라는 방식으로 class에 참조한 것을 확인할 수 있습니다. 이것이 member 접근을 한다는것을 나타냅니다.

이 마지막줄에서, 우리는 Greeter class를 new 생성자를 사용하여 생성했습니다. 이것은 우리가 이전에 정의했던 constructor를 호출하고, Greeter shape를 이용하여 새로운 오브젝트를 생성하고, constructor를 호출하여 초기화합니다

 

Inheritance

TS에서, 우리는 일반적인 object-oriented 패턴을 사용합니다. class 기반 프로그래밍의 가장 기초 패턴은 존재하는 class를 새로운 것을 생성하기 위해 상속을 사용하여 존재하는 class를 확장할 수 있도록 해주는 것 입니다.

class Animal {
  move (distanceInMeters: number = 0) {
    console.log(`Animal moved ${distanceInMeters}`)
  }
}

class Dog extends Animal {
  bark () {
    console.log('Woof Woof!')
  }
}

const dog = new Dog()
dog.bark()
dog.move(10)
dog.bark()

이예제는 가장 기초적인 상속 특징을 보여줍니다 - class는 프로퍼티와 메서드를 기본 class로부터 상속합니다. 새로 생성된 Dog같은 Derived class는 subclass라고 부르고, 기본 class는 superclass라고 부릅니다.

class Animal {
  name: string
  constructor (theName: string) {
    this.name = theName
  }
  move (distanceInMeters: number = 0) {
    console.log(`Animal moved ${distanceInMeters}`)
  }
}

class Snake extends Animal {
  constructor (name: string) {
    super(name)
  }
  move (distanceInMeters = 5) {
    console.log("Slithering...");
    super.move(distanceInMeters)
  }
}


class Horse extends Animal {
  constructor (name: string) {
    super(name)
  }
  move (distanceInMeters = 45) {
    console.log('Galloping...')
    super.move(distanceInMeters)
  }
}

const sam = new Snake('Sammpy the Python')
const tom = new Horse('Tommy the Palomino')

sam.move()
tom.move(34)

// Slithering...
// Sammy the Python moved 5m.
// Galloping...
// Tommy the Palomino moved 34m.

이 예제는 이전 예제와 다르게 derived 클래스의 constructor function이 base class의 constructor를 실행하는 super()를 호출해야만 한다는 것 입니다.  constructor body에서 this에서 property에 접근하기 전에, 우리는 super()를 호출해야만 합니다. 이것은 TS에서 강제하는 중요한 규칙입니다.

base class에 존재하는 methods를 subclass에 전문화된 메서드로 override하는 방법도 나와있습니다. Sname와 Horse 둘다 Animal 의 move 메서드를 override 하는 move 메서드를 가지는데, 각각의 클래스를 구체화하도록 해줍니다. 

 

Public, private, and protected modifiers

Public by default

그동안은 우리 프로그램 전체에 우리가 선언한 멤버들에 자유롭게 접근할 수 있었습니다. 만약 우리가 다른 언어에서 class 개념과 익숙하다면, 우리는 위 예제들은 public 이라는 단어를 사용하지 않았다는것을 알 수 있습니다. TS에서는, 기본적으로 모든 멤버들은 public이 기본입니다.

우리는 명확하게 public 멤버들을 여전히 마킹할 수는 있습니다.

class Animal {
  public name: string
  
  public constructor (theName: string) {
    this.name = theName
  }
  
  public move (distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`)
  }
}

ECMAScript Private Fields

TS는 private field를 위한 새로운 JS 문법을 도입했습니다.

class Animal {
  #name: string
  constructor (theName: string) {
    this.#name = theName
  }
}

new Animal('Cat').#name
// #name is not accesible because it has a private identifier

Understanding Typescript's private

TS는 private로 마크된 멤버를 선언할 수 있습니다. private 멤버를 포함한 클래스 밖에서는 해당 변수에 접근할 수 없습니다.

 

class Animal {
  private name: string
  
  constructor (theName: string) {
    this.name = theName
  }
}

new Animal('Cat').name
// Property 'name' is private and only accessible within class 'Animal'.​

TS는 구조 타입 시스템입니다. 어디서 오든지 상관없이 두개의 다른 타입들을 비교할 때 , 만약 모든 멤버들의 타입이 양립 가능하다면, 타입이 양립할 수 있다고 말합니다.

그러나, private과 protexted 멤버들을 가진 타입들을 피교할 때, 우리는 이 타입들은 다르게 취급합니다. 두개의 타입이 양립 가능하도록 고려하기 위해서, 만약 그중 하나가 private 멤버를 가진다면, 딜다른 하나는 같은곳에서 선언된 private 멤버를 가져야합니다. 비슷하게, protexted 멤버에 적용됩니다.

class Animal {
  private name: string
  constructor (theName: string) {
    this.name = theName
  }
}

class Rhino extends Animal {
  constructor () {
    super('Rhino')
  }
}

class Employee {
  private name: string
  constructor (theName: string) {
    this.name = theName
  }
}

let animal = new Animal('Goat')
let rhino = new Rhino()
let employee = new Employee('Bob')

animal = rhino
animal = employee
// Type 'Employee' is not assignable to type 'Animal'.
// Types have separate declarations of a private property 'name'.

Employee는 Animal과 모양이 같은 class 입니다. 우리는 이 class의 인스턴스를 생성하고, 각각에게 어떤 일이 발생할지 확인합니다. Animal과 Rhino는 Animal의 'private name: string'이라는 동일한 선언에서 나온 그들의 모양의 private 면을 공유하기 때문에, 그들은 양립할 수 있다. 그러나, Employee의 경우에는 아닙니다. Employee로부터 Animal에게 할당할 때, 양립할 수 없다는 타입에러가 나옵니다. Employee는 name이라는 private 멤버를 가지고있지만, Animal에서 선언한 것이 아닙니다.

Understanding protected

protected 수식어는 멤버들이 확장된 class안에서 접근 가능한 [보호된] 상태로 선언되었단 점을 제외하고 private 수식어와 매우 유사합니다.

class Person {
  protected name: string
  constructor (name: string) {
    this.name = name
  }
}

class Employee extends Person {
  private department: string

  constructor (name: string, department: string) {
    super(name)
    this.department = department
  }

  public getElevatorPitch () {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`
  }
}

let howard = new Employee('Howard', 'Sales')
console.log(howard.getElevatorPitch())
console.log(howard.name) // Howard
// Error!! Property 'name' is protected and only accessible within class 'Person' and its subclasses.

 

우리가 Person의 밖에서 name을 사용하지 못하는동안, 우리가 Person에서부터 Employee를 확장했기 때문에 Employee 의 인스턴스 메서드 안에서 여전히 사용할 수 있는 것을 보세요.

constructor는 protected로 마킹될 수 있습니다. 즉, 클래스는 포함하는 클래스 외부에서 인스턴스화할 수 없지만 확장할 수 있습니다.

class Person {
  protected name: string;
  protected constructor(theName: string) {
    this.name = theName;
  }
}

// Employee can extend Person
class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John");
Constructor of class 'Person' is protected and only accessible within the class declaration.

Parameter properties

class Octopus {
  readonly numberOfLegs: number = 8;
  constructor(readonly name: string) {}
}

let dad = new Octopus("Man with the 8 strong legs");
console.log(dad.name);

 

반응형

'FRONTEND > Javascript' 카테고리의 다른 글

Map, WeakMap, Set, WeakSet  (0) 2021.05.09
bundle size 와 Javascript Performance  (0) 2021.03.03
4) Typescript - LiteralTypes  (0) 2021.02.08
2) Typescript - interface  (0) 2021.01.25
1) Typescript 기본 타입 사용법 및 정리  (0) 2021.01.22

댓글