독립 함수를 이해하는 것과 동일한 방식으로 메서드와 생성자를 이해한다.
// 메서드
class Hello {
printHello(name: string) {
console.log(`Hello! ${name}`);
}
}
new Hello().printHello('Kim');
new Hello().printHello(); // Error: Expected 1 arguments, but got 0.
// 생성자
class Hello {
constructor(name: string) {
console.log(`Hello! ${name}`);
}
}
new Hello('Kim');
new Hello(); // Error: Expected 1 arguments, but got 0.
class FiedTrip {
destination: string;
constructor(destination: string) {
this.destination = destination;
this.nonexistent = destination; // Error: Property 'nonexistent' does not exist on type 'FiedTrip'.
}
}
엄격한 초기화 검사는 값이 할당되지 않고 선언된 속성이 생성자에서 할당되었는지 확인한다.
class WithValue {
a = 0; // OK: 값 할당
b: number; // OK: constructor에서 할당
c: number | undefined; // OK: undefined가 되는 것 허용됨
d: string; // Error: Property 'd' has no initializer and is not definitely assigned in the constructor.
constructor() {
this.b = 1;
}
}
!
키워드를 사용해서 엄격한 초기화 검사를 비활성화 할 수 있다. class WithValue {
a = 0; // OK: 값 할당
b: number; // OK: constructor에서 할당
c: number | undefined; // OK: undefined가 되는 것 허용됨
d!: string; // OK: 엄격한 초기화 검사 비활성화
constructor() {
this.b = 1;
}
}
?
키워드를 사용해서 속성을 옵션으로 선언할 수 있다. class WithValue {
a = 0;
b: number;
c: number | undefined;
d?: string; // OK: 옵션
constructor() {
this.b = 1;
}
}
readonly
키워드를 사용하면 속성을 읽기 전용으로 만든다. class WithValue {
a = 0;
b: number;
c: number | undefined;
readonly d: string; // 읽기 전용
constructor() {
this.b = 1;
this.d = 'readonly';
}
emphasize() {
this.d += "!"; // Error: Cannot assign to 'd' because it is a read-only property.
}
}
readonly
로 선언된 속성은 리터럴 타입으로 유추된다. class RandomString {
readonly stringA: string = "Hello world!"; // 더 넓은 원시값
readonly stringB = "Hello world!"; // 리터럴 타입
constructor() {
if(Math.random() > 0.5) {
this.stringA = "Hi~"; // OK
this.stringB = "Hi~"; // Error: Type '"Hi~"' is not assignable to type '"Hello world!"'.
}
}
}
❗️진정한 읽기 전용이 필요하다면
#
private 필드나get()
함수 속성을 사용하는게 좋다.
클래스로 타입을 지정할수도 있다.
class Person {
hello() {
console.log("hello!");
}
}
let person: Person;
person = new Person(); // OK
person = "Hi!"; // Error: Type 'string' is not assignable to type 'Person'.
선언된 클래스와 객체의 구조가 똑같다면 클래스에 할당할 수 있다. (타입스크립트는 structural typing)
class Person {
hello() {
console.log("hello!");
}
}
let person: Person;
person = new Person(); // OK
person = {
hello() {
console.log("hihi");
},
}; // 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++) {
console.log("...studying...");
}
}
}
class Slacker implements Learner {
// Error: Class 'Slacker' incorrectly implements interface 'Learner'.
}
인터페이스에서 클래스의 메서드, 속성 타입을 유추하지 않는다.
class Slacker implements Learner {
name; // Error: Member 'name' implicitly has an 'any' type.
study(hours) {} // Error: Parameter 'hours' implicitly has an 'any' type.
}
타입스크립트의 클래스는 다중 인터페이스를 구현해 선언할 수 있다.
interface A {
name: string;
}
interface B {
hello: () => void;
}
class Hello implements A, B {
name: string;
constructor(name: string) {
this.name = name;
}
hello() {
console.log("hello");
}
}
class Empty implements A, B {
// Error: Class 'Empty' incorrectly implements interface 'A'.
// Property 'name' is missing in type 'Empty' but required in type 'A'.
// Class 'Empty' incorrectly implements interface 'B'.
// Property 'hello' is missing in type 'Empty' but required in type 'B'.
}
클래스에 선언된 모든 메서드나 속성은 하위 클래스에서 사용할 수 있다.
class Animal {
sounds() {
console.log("🔊");
}
}
class Rabbit extends Animal {
jump() {
console.log("🐰⬆️⬆️⬆️");
}
}
const rabbit = new Rabbit();
rabbit.sounds(); // OK: 기본 클래스에 정의됨
rabbit.jump(); // OK: 하위 클래스에 정의됨
하위 클래스의 인터페이스는 기본 클래스의 모든 멤버를 가진다. 따라서 기본 클래스의 인스턴스가 필요한 모든 곳에서 사용할 수 있다.
class Lesson {
subject: string;
constructor(subject: string) {
this.subject = subject;
}
}
class OnlineLesson extends Lesson {
url: string;
constructor(subject: string, url: string) {
super(subject);
this.url = url;
}
}
let lesson: Lesson;
lesson = new Lesson("coding");
lesson = new OnlineLesson("coding", "oreilly.com");
let onlineLesson: OnlineLesson;
onlineLesson = new Lesson("coding"); // Error: Property 'url' is missing in type 'Lesson' but required in type 'OnlineLesson'.
onlineLesson = new OnlineLesson("coding", "oreilly.com"); // OK
super
키워드를 통해 기본 클래스 생성자를 호출해야 한다. class GradeAnnouncer {
message: string;
constructor(grade: number) {
this.message = grade >= 65 ? "Maybe next time..." : "You pass!";
}
}
class PassingAnnouncer extends GradeAnnouncer {
constructor() {
super(100);
}
}
class FailAnnouncer extends GradeAnnouncer {
constructor() {
// Error: Constructors for derived classes must contain a 'super' call.
}
}
하위 클래스의 메서드가 기본 클래스의 메서드에 할당될 수 있는 한 하위 클래스는 기본 클래스와 동일한 이름으로 새 메서드를 다시 선언할 수 있다.
class GradeCounter {
// countGrades 메서드는 number 타입을 반환한다.
countGrades(grades: string[], letter: string) {
return grades.filter(grade => grade === letter).length;
}
}
// 부모 클래스의 메서드를 재정의
class FailureCounter extends GradeCounter {
countGrades(grades: string[]) {
return super.countGrades(grades, "F");
}
}
class AnyFailureCounter extends GradeCounter {
// 재정의된 countGrades 메서드는 boolean 타입 반환해서 에러가 발생한다.
countGrades(grades: string[]) {
return super.countGrades(grades, "F") !== 0;
}
// Error: Property 'countGrades' in type 'AnyFailureCounter'
// is not assignable to the same property in base type 'GradeCounter'.
}
// 예상한 타입: number
// 실제 타입: boolean
class Assignment {
grade?: number; // number | undefined
}
class GradedAssignment extends Assignment {
grade: number; // number
constructor(grade: number) {
super();
this.grade = grade;
}
}
기본 클래스 속성의 타입을 확장할 수 없다.
class NumberValue {
value = 0; // number
}
class NumberAndStringValue extends NumberValue {
value = Math.random() > 0.5 ? 1 : "str"; // string | number
// Error: Property 'value' in type 'NumberAndStringValue'
// is not assignable to the same property in base type 'NumberValue'.
}
abstract
키워드를 추가한다. abstract class School {
readonly name: string;
constructor(name: string) {
this.name = name;
}
// 메서드 구현 건너뛰고 타입만 선언함
abstract getStudentTypes(): string[];
}
class Preschool extends School {
getStudentTypes() {
return ["preschooler"];
}
}
// 기본 클래스의 메서드를 구현하지 않아서 에러 발생
class Absence extends School {
// Error: Non-abstract class 'Absence' does not implement
// inherited abstract member 'getStudentTypes' from class 'School'.
}
// 추상 클래스는 타입 애너테이션으로 사용할 수 있다.
let school: School;
// 하지만 새 인스턴스 생성은 불가능
school = new School(); // Error: Cannot create an instance of an abstract class.
public(기본값)
모든 곳에서 누구나 접근 가능 protected
클래스 내부 or 하위 클래스에서만 접근 가능 private
클래스 내부에서만 접근 가능 #
private 선언은 런타임에도 존재한다. class Base {
isPublicImplicit = 0;
public isPublicExplicit = 1;
protected isProtected = 2;
private isPrivate = 3;
#truePrivate = 4;
}
class SubClass extends Base {
examples() {
this.isPublicImplicit;
this.isPublicExplicit;
this.isProtected;
this.isPrivate; // Error
this.#truePrivate; // Error
}
}
new SubClass().isPublicImplicit;
new SubClass().isPublicExplicit;
new SubClass().isProtected; // Error
new SubClass().isPrivate; // Error
new SubClass().#truePrivate; // Error
readonly
와 함께 표시할 수 있다. readonly
키워드를 작성한다. class Base {
readonly isPublicImplicit = 0;
public readonly isPublicExplicit = 1;
protected readonly isProtected = 2;
private readonly isPrivate = 3;
readonly #truePrivate = 4;
}
static
키워드를 사용해서 클래스 자체 멤버를 선언한다. static
키워드를 단독으로 사용하거나 readonly
, 접근성 키워드를 함께 사용할 수 있도록 지원한다. static
- readonly
순서로 작성한다. class Base {
static readonly isPublicImplicit = 0;
public static readonly isPublicExplicit = 1;
protected static readonly isProtected = 2;
}