8.1 클래스 메서드
타입 스크립트는 독립 함수(standalone function)를 이해하는 것과 동일한 방식으로 메서드를 이해함
매개변수에 타입이나 기본 값을 지정하지 않으면 기본으로 any 타입
메서드 호출 시 허용 가능한 수의 인수가 필요
재귀 함수가 아니라면 대부분 반환 타입을 유추할 수 있음
클래스 생성자(constructor)도 전형적인 클래스 메서드 처럼 취급됨
class Greeted {
constructor(message: string) {
console.log(`As I always say: ${message}!`);
}
}
new Greeted("take chances, make mistakes, get message");
new Greeted();
// Error: Expected 1 argument, but got 0.
8.2 클래스 속성
클래스의 속성을 읽거나 쓰려면 클래스에 명시적으로 선언해야 함
클래스 속성 이름 뒤에 선택적으로 타입 애너테이션이 붙음
타입스크립트는 생성자 내에서 할당을 통해 멤버를 자동으로 추론하지 않고 명시적 선언을 통해 찾음
클래스 인스턴스가 사용될 때 코드에서 클래스 인스턴스에 존재하지 않는 멤버에 접근 시도할 경우 타입스크립트는 오류를 발생시킴
8.2.1 함수 속성
클래스의 멤버를 호출 가능한 함수로 선언하는 두 가지 구문이 있음
메서드 접근 방식
멤버 이름 뒤에 괄호를 붙임 ex. myMethod(){}
함수를 클래스 프로토타입에 할당하므로 모든 클래스 인스턴스는 동일한 함수 정의를 사용함
값이 함수인 속성을 선언하는 방식
멤버의 속성을 함수 타입으로 선언함 ex. myProperty: () ⇒ {}
클래스의 인스턴스당 새로운 함수가 생성됨
항상 클래스 인스턴스를 가리키도록 하는 () ⇒ 화살표 함수와 함께 사용될 수 있다는 유용함이 있으나 클래스 인스턴스마다 새로운 함수를 만들어야 하므로 시간과 메모리 비용이 들 수 있음 (= 화살표 함수를 사용할 때, 함수 내에서 this가 항상 클래스 인스턴스를 가리키도록 하려면 각 인스턴스마다 새로운 함수를 생성해야 하는데 실행 시간과 메모리 사용량이 증가하는 비용이 따르게 됨을 의미)
매개변수와 반환 타입도 지정할 수 있음
8.2.2 초기화 검사
엄격한 컴파일러 설정이 활성화된 상태에서는 undefined 가 타입 선언에 포함되지 않은 각 속성에 대해 생성자에서 값이 할당되었는지 확인함
엄격한 초기화 검사가 수행되지 않을 경우 값이 할당되지 않아도 올바르게 컴파일은 되지만 런타임 시 오류가 발생함
확실하게 할당된 속성
엄격한 초기화 검사를 적용하면 안되는 속성의 경우 이름 뒤에 !를 추가해 검사를 비활성화 할 수 있음
이 경우 타입스크립트에 속성이 처음 사용되기 전 undefined가 할당됨
!어서션을 추가하여 타입 안정성을 줄이기 보다는 사용하지 않는 방향으로 리팩토링할 것을 권장함
8.2.3 선택적 속성
속성의 이름 뒤에 ?를 추가하여 속성을 옵션으로 선언할 수 있음
생성자에서 선택적 속성을 명시적으로 설정하지 않아도 됨
8.2.4 읽기 전용 속성
선언된 속성 이름 앞에 readonly 키워드를 추가해 속성을 읽기 전용으로 선언할 수 있음
타입 시스템에만 존재하는 키워드로 자바스크립트로 컴파일 시 삭제됨
선언된 위치 또는 생성자에서 초깃값만 할당할 수 있음
원시 타입의 초깃값을 갖는 readonly 선언 속성의 경우 리터럴 타입으로 유추되므로 생성자에서 값을 할당하기 위해서는 속성에 타입 애너테이션으로 타입을 지정해주어야 함
8.3 타입으로서의 클래스
클래스 선언은 런타임 값을 생성하는 동시에 (클래스 자체) 타입을 생성하여 타입 주석에서 사용할 수 있음 즉, 클래스는 런타임과 타입 시스템 양쪽에서 동시에 역할을 한다는 점에서 상태적으로 독특함
클래스의 동일한 멤버를 모두 포함하는 모든 객체 타입을 클래스에 할당할 수 있는 것으로 간주함
withSchoolBus는 SchoolBus 타입의 매개변수를 받음. 매개변수로 SchoolBus 클래스 인스턴스처럼 타입이 () ⇒ string[]인 getAbilities 속성을 가진 모든 객체를 할당할 수 있음 (자주 사용되는 방식은 아님)
class SchoolBus {
getAbilities() {
return ["magic", "shapeshifting"];
}
}
function withSchoolBus(bus: SchoolBus) {
console.log(bus.getAbilities());
}
withSchoolBus(new SchoolBus()); // Ok
// Ok
withSchoolBus({
getAbilities: () => ["transmogrification"],
});
withSchoolBus({
getAbilities: () => 123,
// Error: Type 'number' is not assignable to type 'string[]'
})
8.4 클래스와 인터페이스
클래스 이름 뒤에 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 Slacker implements Learner {
// Error: Class 'Slacker' incorrectly implements interface 'Learner'.
// Property 'study' is missing in type 'Slacker'
// but required in type 'Learner'.
name = "Rocky";
}
타입스크립트는 인터페이스에서 클래스의 메서드 또는 속성 타입을 유추하지 않음. 위의 예제에서 Slacker 클래스에 study(hours){} 메서드를 추가했을 경우 타입스크립트는 타입 에너테이션을 지정하지 않는 한 hours 매개변수를 any로 간주함
인터페이스를 구현하는 것은 순전히 안정성 검사를 위해서임
인터페이스의 멤버를 클래스 정의로 복사하지 않으나 인터페이스 구현 시 클래스 인스턴스가 사용되는 곳에서가 아닌 클래스 정의에서 타입 체커에 의도를 알리고 타입 오류를 나타내기 위함임
8.4.1 다중 인터페이스 구현
클래스는 다중 인터페이스를 구현해 선언할 수 있음
인터페이스 이름 사이에 쉼표를 넣어 구분하고 개수 제한은 없음
class ReportCard implements Graded, Reporter {
}
구현하고자 하는 모든 인터페이스의 속성을 가지도록 해야 타입 오류가 발생하지 않음
두 개의 충돌하는 인터페이스를 구현하는 클래스를 선언하려고 할 때는 클래스에 타입 오류가 발생함
interface AgeIsANumber {
age: number;
}
interface AgeIsNotANumber {
age: () => string;
}
class AsNumber implements AgeIsANumber, AgeIsNotANumber {
age = 0;
// Error: Property 'age' in type 'AsNumber' is not assignable
// to the same property in base type 'AgeIsNotANumber'.
// Type 'number' is not assignable to type '() => string'.
}
class NotAsNumber implements AgeIsANumber, AgeIsNotANumber {
age() { return ""; }
// Error: Property 'age' in type 'NotAsNumber' is not assignable
// to the same property in base type 'AgeIsANumber'.
// Type '() => string' is not assignable to type 'number'.
}
8.5 클래스 확장
다른 클래스를 확장하거나 하위 클래스를 만드는 개념에도 타입 검사를 추가함
기본 클래스에 선언된 모든 메서드와 속성은 파생 클래스(하위 클래스)에서 사용 가능함
class Teacher {
teach() { ... }
}
class StudentTeacher extends Teacher {
learn() { ... }
}
const teacher = new StudentTeacher();
teacher.teach(); // Ok
teacher.learn(); // Ok
8.5.1 할당 가능성 확장
하위 클래스도 기본 클래스의 멤버를 상속함
하위 클래스의 인스턴스는 기본 클래스의 모든 멤버를 가지므로 기본 클래스의 인스턴스가 필요한 모든 곳에서 사용할 수 있음
반대로, 기본 클래스에 하위 클래스가 가지고 있는 모든 멤버가 없다면 하위 클래스가 필요할 때 기본 클래스를 사용할 수 없음
하위 클래스의 선택적 속성은 오류로 처리하지 않음
8.5.2 재정의된 생성자
하위 클래스에서 자체 생성자를 정의하지 않으면 암묵적으로 기본 클래스의 생성자를 사용함
자체 생성자를 선언할 경우 super 키워드를 사용해 기본 클래스의 생성자를 호출해야 함
기본 클래스의 생성자를 호출하기 전에 this 혹은 super에 접근하려고 하는 경우 타입 오류를 보고함
8.5.3 재정의된 메서드
하위 클래스에서 기본 클래스의 메서드와 동일한 이름으로 새 메서드를 다시 선언할 수 있음
주의할 점은 기본 클래스를 사용하는 모든 곳에 하위 클래스를 사용할 수 있으므로 재정의된 메서드의 타입도 기본 메서드 대신 사용할 수 있어야 함
8.5.4 재정의된 속성
하위 클래스에서 기본 클래스의 속성과 동일한 이름으로 속성을 다시 선언할 수 있음
하위 클래스는 기본 클래스와 구조적으로 일치해야 하고 속성을 다시 선언하는 경우 해당 속성을 유니언 타입의 더 구체적인 하위 집합이나 기본 클래스 속성 타입에서 확장되는 타입으로 정의함
예를 들어, 기본 클래스의 속성이 number | undefined 타입일 때, 하위 클래스에서 동일한 이름의 속성을 number 타입으로 선언하는 것은 가능함
그러나 기본 클래스의 속성이 number 타입인데, 하위 클래스에서 동일한 이름의 속성을 number | string 타입으로 선언하는 것은 타입 오류임
8.6 추상 클래스
일부 메서드의 구현을 선언하지 않고, 하위 클래스가 해당 메서드를 제공할 것을 예상해 기본 클래스를 만들 수도 있음
추상화하려는 클래스 이름과 메서드 앞에 abstract 키워드를 추가하면 됨
abstract class School {
readonly name: string;
constructor(name: string) {
this.name = name;
}
abstract getStudentType(): string[];
}
class Preschool extends School {
getStudentTypes() {
return ["preschooler"];
}
}
class Absence extends School { }
// Error: Nonabstract class 'Absence' does not implement
// inherited abstract member 'getStudentTypes' from class 'School'
추상 클래스는 직접 인스턴스화 할 수 없고 추상 클래스가 아닌 하위 클래스를 사용해야 함
8.7 멤버 접근성
자바스크립트에서는 클래스 멤버 이름 앞에 #을 추가해 private 클래스 멤버임을 나타냄
private 클래스 멤버는 해당 클래스 인스턴스에서만 접근 가능함
타입스크립트의 클래스 지원은 자바스크립트의 # 프라이버시보다 먼저 만들어졌음
타입스크립트는 private 클래스 멤버를 지원하지만, 타입 시스템에만 존재하는 클래스 메서드와 속성에 대해 조금 더 미묘한 프라이버시 정의 집합을 허용함(세밀한 차이가 있을 수 있음을 말함)
키워드
public (기본값): 모든 곳에서 누구나 접근 가능
protected: 클래스 내부 또는 하위 클래스에서만 접근 가능
private: 클래스 내부에서만 접근 가능
키워드는 타입 시스템 내에만 존재하며 자바스크립트로 컴파일되면 키워드는 제거됨
타입스크립트의 멤버 접근성은 타입 시스템에서만 존재하지만 자바스크립트의 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;
// 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'.