2023-04-19 · 16 min read · 학습

러닝 타입스크립트 CHAPTER 8 - 클래스

CHAPTER 8 클래스

오늘날 타입스크립트의 클래스 지원은 모든 자바스크립트 언어 기능을 지원하는 많은 기능 중 하나에 불과합니다. 타입스크립트는 클래스 사용이나 다른 인기 있는 자바스크립트 패턴을 권장하지도 막지도 않습니다.

클래스 메서드

타입스크립트는 독립 함수를 이해하는 것과 동일한 방식으로 메서드를 이해합니다. 매개변수 타입에 타입이나 기본값을 지정하지 않으면 any타입을 기본으로 갖습니다. 메서드를 호출하려면 허용 가능한 수의 인수가 필요하고, 재귀 함수가 아니라면 대부분 반환 타입을 유추할 수 있습니다.

class Greeter {
greet(name: string) {
console.log(`${name}, do tour stuff!`)
}
}
new Greeter().greet('Miss Frizzle') // OK
new Greeter().greet()
// Error: Expected 1 arguments, but got 0.

클래스 생성자(constructor)는 매개변수와 관련하여 전형적인 클래스 메서드처럼 취급됩니다.

class Greeted {
constructor(message: string) {
console.log('As I always say: ${message}!')
}
}
new Greeted('take chances, make mistakes, get messy')
new Greeted()
// Error: Expected 1 arguments, but got 0.

클래스 속성

타입스크립트에서 클래스의 속성을 읽거나 쓰려면 클래스에 명시적으로 선언해야 합니다. 타입스크립트는 생성자 내 할당에 대해서 그 멤버가 클래스에 존재하는 멤버인지 추론하려고 시도하지 않습니다.

class FieldTrip {
destination: string
constructor(destination: string) {
this.destination = destination // OK
console.log(`We're going to ${this.destination}!`)
this.nonexistent = destination
// Error: Property 'nonexistent' does not exist on type 'FieldTrip'.
}
}

클래스 속성을 명시적으로 선언하면 타입스크립트는 클래스 인스턴스에서 무엇이 허용되고, 허용되지 않는지 빠르게 이해할 수 있습니다.

const trip = new FieldTrip('planetarium')
trip.destination // OK
trip.nonexistent
// Error: Property 'nonexistent' does not exist on type 'FieldTrip'.

함수 속성

자바스크립트에는 클래스의 멤버를 호출 가능한 함수로 선언하는 두 가지 구문이 있습니다.

멤버 이름 뒤에 괄호를 붙이는 메서드 접근 방식은 함수를 클래스 프로토타입에 할당하므로 모든 클래스 인스턴스는 동일한 함수 정의를 사용합니다.

다음 WithMethod 클래스는 모든 인스턴스가 참조할 수 있는 myMethod메서드를 선언합니다.

class WithMethod {
myMethod() {}
}
new WithMethod().myMethod === new WithMethod().myMethod // true

값이 함수인 속성을 선언하는 방식도 있습니다. 이렇게 하면 클래스의 인스턴스당 새로운 함수가 생성되며, 항상 클래스 인스턴스를 가리켜야 하는 화살표 함수에서 this 스코프를 사용하면 클래스 인스턴스당 새로운 함수를 생성하는 시간과 메모리 비용 측면에서 유용할 수 있습니다.

WithProperty클래스는 이름이 myProperty인 단일 속성을 포함하며 각 클래스 인스턴스에 대해 다시 생성되는 () => void 타입입니다.

class WithProperty {
myProperty: = () => {}
}
new WithProperty().myProperty === new WithProperty().myProperty // false

함수 속성에는 클래스 메서드와 독립 함수의 동일한 구문을 사용해 매개변수와 반환 타입을 지정할 수 있습니다. 결국 함수 속성은 클래스 멤버로 할당된 값이고, 그 값은 함수입니다.

class WithPropertyParameters {
takesParameters = (input: boolean) => (input ? 'Yes' : 'No')
}
const instance = new WithPropertyParameters()
instance.takesParameters(true) // OK
instance.takesParameters(123)
// Error: Argument of type 'number' is not assignable to parameter of type 'boolean'.

초기화 검사

undefined 타입으로 선언된 각 속성이 생성자에서 할당되었는지 확인합니다. 엄격한 초기화 검사를 통해 클래스 속성에 값을 할당하지 않는 실수를 예방할 수 있습니다.

class WithValue {
immediate = 0 // OK
later: number // OK (constructor에서 할당)
mayBeUndefined: number | undefined // OK (undefined가 되는 것이 허용됨)
unused: number
// Error: Property 'unused' has no initializer
// and is not definitely assigned in the constructor.
constructor() {
this.later = 1
}
}

확실하게 할당된 속성

클래스 생성자 다음에 클래스 속성을 의도적으로 할당하지 않는 경우가 있을 수도 있습니다. 이름 뒤에 !를 추가해 검사를 비활성화하도록 설정합니다. 이렇게 하면 타입스크립트에 속성이 처음 사용되기 전에 undefined 값이 할당됩니다.

class ActivitiesQueue {
pending!: string[] // OK
initialize(pending: string[]) {
this.pending = pending
}
next() {
return this.pending.pop()
}
}
const activities = new ActivitiesQueue()
activities.initialize(['eat', 'sleep', 'learn'])
activities.next()

선택적 속성

클래스에서도 ?를 추가해 속성을 옵션으로 선언할 수 있습니다.

class MissingInitializer {
property?: string
}
new MissingInitializer().property?.length // OK

읽기 전용 속성

readonly 키워드를 통해 속성을 읽기 전용으로 선언합니다. readonly는 타입 시스템에만 존재하며 자바스크립트로 컴파일할 때 삭제됩니다.

class Quote {
readonly text: string
constructor(text: string) {
this.text = text
}
emphasize() {
this.text += '!'
// Error: Cannot assign to 'text' because it is a read-only property.
}
}
const quote = new Quote('There is a briliant child locked inside every student.')
quote.text = 'Ha!'
// Error: Cannot assign to 'text' because it is a read-only property.

진정한 읽기 전용 보호가 필요하다면 # private 필드나 get() 함수 속성 사용을 고려하세요.

타입으로서의 클래스

타입시스템에서의 클래스는 클래스 선언이 런타임 값(클래스 자체)과 타입 애너테이션에서 사용할 수 있는 타입을 모두 생성한다는 점에서 독특합니다.

class Teacher {
sayHello() {
console.log('Take chances, make mistakes, get messy!')
}
}
let teacher: Teacher
teacher = new Teacher() // OK

클래스와 인터페이스

타입스크립트는 클래스 이름 뒤에 implements 키워드와 인터페이스 이름을 추가함으로써 클래스의 해당 인스턴스가 인터페이스를 준수한다고 선언할 수 있습니다.

interface Learner {
name: string
study(hours: number): void
}
class Student implements Learner {
name: string
constructor(name: string) {
this.name = name
}
study(hours: number) {
for (let i = 0; i < hours; i += 1) {
console.log('...studying...')
}
}
}

다중 인터페이스 구현

타입스크립트의 클래스는 다중 인터페이스를 구현해 선언할 수 있습니다. 클래스에 구현된 인터페이스 목록은 인터페이스 이름 사이에 쉼표를 넣고, 개수 제한 없이 사용할 수 있습니다.

클래스 확장

타입스크립트는 다른 클래스를 확장하거나 하위 클래스를 만드는 자바스크립트 개념에 타입 검사를 추가합니다.

class Teacher {
teach() {
console.log('teach')
}
}
class StudnetTeacher extends Teacher {
learn() {
console.log('learn')
}
}
const teacher = new StudnetTeacher()
teacher.teach() // OK (기본 클래스에 정의됨)
teacher.learn() // OK (하위 클래스에 정의됨)

할당 가능성 확장

하위 클래스도 기본 클래스의 멤버를 상속합니다. 하위 클래스의 인스턴스는 기본 클래스의 모든 멤버를 가지므로 기본 클래스의 인스턴스가 필요한 모든 곳에서 사용할 수 있습니다.

class Lesson {
// ~~~
}
class OnlineLesson extends Lesson {
// ~~~
}
let lesson: Lesson
lesson = new Lesson() // OK
lesson = new OnlineLesson() // OK

재정의된 생성자

타입스크립트에서 하위 클래스는 자체 생성자를 정의할 필요가 없습니다. 자체 생성자가 없는 하위 클래스는 암묵적으로 기본 클래스의 생성자를 사용합니다.

자바스크립트에서 하위 클래스가 자체 생성자를 선언하면 super 키워드를 통해 기본 클래스 생성자를 호출해야 합니다. 타입스크립트는 super()를 호출하기 전에 this 또는 super에 접근하려고 하는 경우 타입 오류를 보고합니다.

재정의된 메서드

하위 클래스의 메서드가 기본 클래스의 메서드에 할당될 수 있는 한 하위 클래스는 기본 클래스와 동일한 이름으로 새 메서드를 다시 선언할 수 있습니다.

재정의된 속성

하위 클래스는 새 타입을 기본 클래스의 타입에 할당할 수 있는 한 동일한 이름으로 기본 클래스의 속성을 명시적으로 다시 선언할 수 있습니다.

추상 클래스

때로는 일부 메서드의 구현을 선언하지 않고, 대신 하위 클래스가 해당 메서드를 제공할 것을 예상하고 기본 클래스를 만드는 방법이 유용할 수 있습니다. 추상화하려는 클래스 이름과 메서드 앞에 타입스크립트의 abstract 키워드를 추가합니다.

abstract class School {
readonly name: string
constructor(name: string) {
this.name = name
}
abstract getStudentTypes(): string[]
}
class PreSchool extends School {
getStudentTypes() {
return ['preschooler']
}
}

구현이 존재한다고 가정할 수 있는 일부 메서드에 대한 정의가 없기 때문에 추상 클래스를 직접 인스턴스화할 수 없습니다. 추상 클래스가 아닌 클래스만 인스턴스화할 수 있습니다.

멤버 접근성

자바스크립트에서는 클래스 멤버 이름 앞에 #을 추가해 private 클래스 멤버임을 나타냅니다. private 클래스 멤버는 해당 클래스 인스턴스에서만 접근할 수 있습니다.

타입스크립트에서는 조금 더 미묘한 프라이버시 정의 집합을 허용합니다.

  • public(기본값): 모든 곳에서 누구나 접근 가능
  • protected: 클래스 내부 또는 하위 클래스에서만 접근 가능
  • private: 클래스 내부에서만 접근 가능

이러한 키워드는 순수하게 타입 시스템 내에 존재합니다. 코드가 컴파일되면 다른 모든 타입 시스템 구문과 함께 키워드도 제거됩니다.

class Base {
isPublicImplicit = 0
public isPublicExplicit = 1
protected isProtected = 2
private isPrivate = 3
#truePrivate = 4
}
class Subclass extends Base {
examples() {
this.isPublicImplicit // OK
this.isPublicExplicit // OK
this.isProtected // OK
this.isPrivate
// Error: Property 'isPrivate' is private and only accessible within class 'Base'.
this.#truePrivate
// Error: Property '#truePrivate' is not accessible outside
// class 'Base' because it has a private identifier.
}
}
new Subclass().isPublicImplicit // OK
new Subclass().isPublicExplicit // OK
new Subclass().isProtected
// Error: Property 'isProtected' is protected
// and only accessible within class 'Base' and its subclasses.
new Subclass().isPrivate
// Error: Property 'isPrivate' is private and only accessible within class 'Base'.

타입스크립트의 멤버 접근성은 타입 시스템에만 존재하는 반면 자바스크립트의 private 선언은 런타임에도 존재한다는 점이 주요 차이점입니다. protected 또는 private으로 선언된 타입스크립트 클래스 멤버는 명시적으로 또는 암묵적으로 public으로 선언된 것처럼 동일한 자바스크립트 코드로 컴파일됩니다. 접근성 키워드는 자바스크립트로 컴파일될 때 제거되며 자바스크립트 런타임에서는 # private 필드만 진정한 private입니다.

정적 필드 제한자

자바스크립트는 static 키워드를 사용해 클래스 자체에서 멤버를 선언합니다. 타입스크립트는 static 키워드를 단독으로 사용하거나 readonly와 접근성 키워드를 함께 사용할 수 있도록 지원합니다. 접근성 키워드, static, readonly 순서입니다. static 클래스 필드에 대해 readonly와 접근성 제한자를 사용하면 해당 필드가 해당 클래스 외부에서 접근하거나 수정되는 것을 제한하는 데 유용합니다.

마치며

  • 클래스 메서드 및 속성 선언과 사용법
  • 읽기 전용(readonly) 또는 선택적 속성으로 표시하기
  • 타입 애너테이션에서 타입으로 클래스 이름 사용하기
  • 클래스 인스턴스 형태를 적용하기 위한 인터페이스 구현하기
  • 하위 클래스에 대한 할당 가능성재정의 규칙으로 클래스 확장하기
  • abstract 클래스와 abstract 메서드로 표시하기
  • 클래스 필드에 타입 시스템 제한자 추가하기