TypeScript는 Javascript ES6부터 도입된 class
keyword를 서포트한다.
자바스크립트의 클래스는 다른 언어들에 통상적으로 존재하는 제한자를 다양하게 지원하지는 않는데, 타입스크립트에서 이 부분을 보완해준다는 점에서 유용할 수 있다.
public
어디서든 접근할 수 있는 멤버이다. keyword 없이 정의하면 자동으로 public 멤버가 되며 public
키워드로 명시적으로 나타낼 수도 있다.
protected
서브클래스와 클래스 정의 내부에서만 접근 가능한 멤버이다.
- 서브클래스 정의시 상속받은 멤버의 visibility를 override하여 변경할 수는 있다.
- 같은 부모 클래스로부터 파생된 다른 서브클래스의
protected
member에 접근할수는 없다.
private
클래스 정의 내부에서만 접근 가능한 멤버이다. 서브클래스에서도 접근하지 못한다.
- 파생 클래스에서 상속받은 멤버의 visibility도 변경할 수 없다.
- cross-instance
private
접근이 가능하다. 즉, 같은 클래스내의 다른 인스턴스 멤버에 서로 접근이 가능하다.
class A {
private x = 10;
public sameAs(other: A) {
// No error
return other.x === this.x;
}
}
TS의 private vs JS의 private accessibility modifier(#)
TypeScript의 visibiliity 제한자는 여타 다른 타입스크립트 문법과 마찬가지로 컴파일시점의 타입체킹 동안에만 유효하다.
즉, JavaScript 런타임에서는 in, bracket notation 등으로 private, protected 요소에 액세스할 수 있다. (soft private)
JavaScript로 트랜스파일링된 결과물을 봐도 public, private, protected 속성별 차이가 없다.
class MySafe {
private secretKey = 12345;
}
const s = new MySafe();
// Not allowed during type checking
console.log(s.secretKey);
// OK
console.log(s["secretKey"]);
ES2022이후부터 JavaScript에서도 class 내부 속성 선언시 앞에 #을 붙여 private 멤버로 생성할 수 있다.
이렇게 명시된 필드는 런타임에서도 private하게 남아있다. (hard private)
아래 예시코드의 #barkAmount는 외부에서 접근이 불가능하다.
class Dog {
sound = "bow wow";
#barkAmount = 0;
constructor() {}
bark(){
console.log(this.sound);
this.#barkAmount += 1;
}
}
const dog = new Dog();
dog.bark();
// error. Property '#barkAmount' is not accessible outside class 'Dog' because it has a private identifier.
console.log(dog.#barkAmount)
위의 코드는 ES2022이전 버전에서는 다음과 같이 WeakMap을 이용하는 방식으로 트랜스파일링된다.
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _Dog_barkAmount;
class Dog {
constructor() {
this.sound = "bow wow"; // public 멤버는 class 내부에 정의되고
_Dog_barkAmount.set(this, 0); // private 멤버는 외부의 WeakMap에 저장한다.
}
bark() {
console.log(this.sound);
__classPrivateFieldSet(this, _Dog_barkAmount, __classPrivateFieldGet(this, _Dog_barkAmount, "f") + 1, "f");
}
}
_Dog_barkAmount = new WeakMap();
const dog = new Dog();
dog.bark();
static
클래스의 특정 인스턴스와 연관되지 않고 클래스 constructor object 자체로 접근가능한 멤버이다.
visibility modifiers(public
, protected
, public
)와 같이 쓰일 수 있다.
- 이때 static method의 이름은 Function의 built-in static name들과 겹치지 않아야한다. (JavaScript에서 class는 Function이기 때문이다)
readonly
수정 불가능한 읽기전용 멤버임을 의미한다. visibility modifiers(public
, protected
, public
)와 같이 쓰일 수 있다.
Parameter Properties
contructor의 parameter에 제한자를 명시하는 방법으로도 클래스내 멤버를 선언할 수 있다.
class Params {
public readonly x:number;
protected y: number;
private z: number;
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
}
위의 코드를
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
parameter properties를 이용하여 축약해서 작성하는 것이 가능하다.
--strictPropertyInitialization
--strictPropertyInitialization
옵션을 사용하면 내부 속성이 꼭 초기화단계에 정의되어야한다.
꼭 constructor에서 직접 초기화되어야하며 다른 method를 호출하여 method내에서 초기화 되면 오류가 발생한다. (파생 클래스가 메서드 오버라이드하면서 초기화 누락될 수 있기때문.)
만약 contructor에서 초기화되지 않는 것을 의도한다면 변수명 옆에 definite assignment assertion operator(!
)를 사용하면 된다.
class OKGreeter {
// Not initialized, but no error
name!: string;
}