TypeScript(타입스크립트) 클래스

·2026년 1월 23일

type-script

목록 보기
5/9

클래스

클래스를 이용해서 만든 객체 → 인스턴스

클래스는 붕어빵 틀(설계도)이고, 인스턴스는 그 틀에서 찍어낸 붕어빵(실체)라고 생각하면 되는데, 자바스크립트는 원래 클래스가 없었지만, 객체 지향 프로그래밍을 쉽게 하기 위해 ES6부터 도입되었다.

하지만 자바스크립트의 클래스는 사실 진짜 객체지향 프로그래밍처럼 보이기 위한 프로토타입을 보기 좋게 포장한 이른바 문법적 설탕(Syntactic Sugar)이다.

ES5

// 1. 함수로 틀을 만듦
function Student(name) {
  this.name = name;
}

// 2. 프로토타입(유전자)에 메서드를 심음
Student.prototype.study = function() {
  console.log("공부함");
};

var s = new Student("철수");
s.study();

ES6

class Student {
  constructor(name) {
    this.name = name;
  }

  // 내부적으로는 'Student.prototype.study'에 저장됨
  study() {
    console.log("공부함");
  }
}

const s = new Student("철수");
s.study();

class 키워드를 써도 결국 자바스크립트는 내부적으로 함수(function)와 프로토타입을 연결해서 돌린다.

암튼 그래도 자바스크립트의 클래스에 대해 알아보자면, 아래와 같이 사용하게 된다.

class Student {
  // 필드
  name;
  grade;
  age;

  // 생성자
  constructor(name, grade, age) {
    this.name = name;
    this.grade = grade;
    this.age = age;
  }

  // 메서드
  study() {
    console.log("열심히 공부 함");
  }

  introduce() {
    console.log(`안녕하세요!`);
  }
}

let studentB = new Student("홍길동", "A+", 27);

console.log(studentB); // Student {name: "홍길동", grade: "A+", age: 27} // 인스턴스 => 스튜던트 인스턴스
studentB.study(); // 열심히 공부 함
studentB.introduce(); // 안녕하세요!

this 활용하기

class Student {
  (...)

  introduce() {
    console.log(`안녕하세요 ${this.name} 입니다!`);
  }
}

let studentB = new Student("홍길동", "A+", 27);

studentB.introduce(); // 안녕하세요 홍길동 입니다!

상속

만약 앞서 만든 Student 클래스를 기반으로 추가적인 필드와 메서드를 갖는 클래스를 선언하고 싶다면 다음과 같이 상속을 이용하면 된다.

그런데 이때 StudentDeveloper 클래스에서 Student 클래스의 생성자를 함께 호출해줘야 합니다. 그렇지 않으면 생성되는 객체의 name, grade, age 값이 제대로 설정되지 않는다. 따라서 다음과 같이 super 라는 메서드를 호출해야다.

class StudentDeveloper extends Student {
  // 필드
  favoriteSkill;

  // 생성자
  constructor(name, grade, age, favoriteSkill) {
    super(name, grade, age);
    this.favoriteSkill = favoriteSkill;
  }

  // 메서드
  programming() {
    console.log(`${this.favoriteSkill}로 프로그래밍 함`);
  }
}

타입스크립트의 클래스

타입스크립트에서는 클래스의 필드를 선언할 때 타입 주석으로 타입을 함께 정의해주어야 한다.

그렇지 않으면 함수 매개변수와 동일하게 암시적 any 타입으로 추론되는데 엄격한 타입 검사 모드(strict 옵션이 true로 설정되어 있을 경우)일 때에는 오류가 발생하게 한다.
추가로 생성자에서 각 필드의 값을 초기화 하지 않을 경우 초기값도 함께 명시해주어야 한다.

class Employee {
  // 필드
  name: string = "";
  age: number = 0;
  position: string = "";

  // 메서드
  work() {
    console.log("일함");
  }
}

생성자 함수에서 필드의 값들을 잘 초기화 해 준다면 필드 선언시의 초기값은 생략 가능하다.

class Employee {
  // 필드
  name: string = "";
  age: number = 0;
  position: string = "";

  // 생성자
  constructor(name: string, age: number, position: string) {
    this.name = name;
    this.age = age;
    this.position = position;
  }

  // 메서드
  work() {
    console.log("일함");
  }
}

객체의 특정 프로퍼티를 선택적 프로퍼티로 만들고 싶다면 다음과 같이 필드의 이름 뒤에 물음표를 붙여주면된다.

class Employee {
  // 필드
  name: string = "";
  age: number = 0;
  position?: string = "";

  // 생성자
  constructor(name: string, age: number, position: string) {
    this.name = name;
    this.age = age;
    this.position = position;
  }

  // 메서드
  work() {
    console.log("일함");
  }
}

클래스 타입

타입스크립트의 클래스는 타입으로도 사용할 수 있다. 클래스를 타입으로 사용하면 해당 클래스가 생성하는 객체의 타입과 동일한 타입이 된다.

class Employee {
  (...)
}

const employeeC: Employee = {
  name: "",
  age: 0,
  position: "",
  work() {},
};

따라서 이 변수는 name, age, position 프로퍼티와 work 메서드를 갖는 객체 타입이 된다.

상속

타입스크립트에서 클래스의 상속을 이용할 때 파생 클래스(확장하는 클래스)에서 생성자를 정의 했다면 반드시 super 메서드를 호출해 슈퍼 클래스(확장되는 클래스)의 생성자를 호출해야 하며,

호출 위치는 생성자의 최상단 이어야만 한다. super(name, age, position);

class ExecutiveOfficer extends Employee {
  officeNumber: number;

  constructor(
    name: string,
    age: number,
    position: string,
    officeNumber: number
  ) {
    super(name, age, position);
    this.officeNumber = officeNumber;
  }
}

접근제어자(Access Modifier)

접근 제어자는 타입스크립트에서만 제공되는 기능으로 클래스의 특정 필드나 메서드를 접근할 수 있는 범위를 설정하는 기능이다.

객체지향 프로그래밍을 할때 주요한 개념이라고 한다!

타입스크립트에서는 다음과 같은 3개의 접근 제어자를 사용할 수 있다.

  • public : 모든 범위에서 접근 가능
  • private : 클래스 내부에서만 접근 가능
  • proteced : 클래스 내부 또는 파생 클래스 내부에서만 접근 가능

Public

public은 공공의 라는 뜻으로 어디서든지 이 프로퍼티에 접근할 수 있음을 의미

class Employee {
  // 필드
  (public) name: string;      // 자동으로 public
  (public) age: number;       // 자동으로 public
  (public) position: string;  // 자동으로 public

  // 생성자
  constructor(name: string, age: number, position: string) {
    this.name = name;
    this.age = age;
    this.position = position;
  }

  // 메서드
  work() {
    console.log("일함");
  }
}

const employee = new Employee("이정환", 27, "devloper");

employee.name = "홍길동";
employee.age = 30;
employee.position = "디자이너";

필드의 접근 제어자를 지정하지 않으면 기본적으로 public 접근 제어자를 갖게 된다.

Private

특정 필드나 메서드의 접근 제어자를 private으로 설정하면 클래스 내부에서만 이 필드에 접근할 수 있게된다. (상속받은 클래스에서도 접근 불가)

class Employee {
  // 필드
  private name: string; // private 접근 제어자 설정
  public age: number;
  public position: string;

  ...

  // 메서드
  work() {
    console.log(`${this.name}이 일함`); // 여기서는 접근 가능
  }
}

const employee = new Employee("이정환", 27, "devloper");

employee.name = "홍길동"; // ❌ 오류
employee.age = 30;
employee.position = "디자이너";

Protected

proteced 접근제어자는 private과 public의 중간으로 클래스 외부에서는 접근이 안되지만 클래스 내부와 파생 클래스에서 접근이 가능하도록 설정하는 접근 제어자이다.

class Employee {
  // 필드
  private name: string; // private 접근 제어자 설정
  protected age: number;
  public position: string;

  ...

  // 메서드
  work() {
    console.log(`${this.name}이 일함`); // 여기서는 접근 가능
  }
}

class ExecutiveOfficer extends Employee {
 // 메서드
  func() {
    this.name; // ❌ 오류 private
    this.age; // ✅ 상속받은 클래스에서는 접근 가능
  }
}

const employee = new Employee("이정환", 27, "devloper");

employee.name = "홍길동"; // ❌ private
employee.age = 30; // ❌ 외부에서는 접근 불가
employee.position = "디자이너";

그리고 추가적으로 readonly와 static 도 선언할 수 있다고 한다.

Readonly

readonly 키워드를 붙이면, 초기화 이후에는 절대 값을 바꿀 수 없다.

변하지 않는 값(예: 고유 ID)을 다룰 때 유용하다.

class Employee {
  readonly id: number; // 한 번 정해지면 못 바꿈!

  constructor(id: number, public name: string) {
    this.id = id;
    this.name = name;
  }
}
const e = new Employee(1, "김철수");
e.id = 2; // ❌ 읽기 전용 속성임

Static

인스턴스를 만들지 않고(new 없이) 클래스 이름으로 바로 접근할 때 쓴다.

class MathUtil {
  static PI = 3.14; // static 속성

  static calculateCircleArea(radius: number) { // static 메서드
    return radius * radius * MathUtil.PI;
  }
}

// new MathUtil() 할 필요 없음!
console.log(MathUtil.PI); 
console.log(MathUtil.calculateCircleArea(5));

필드 생략하기

class Employee {
  // 필드
  private name: string;    // ❌
  protected age: number;   // ❌
  public position: string; // ❌

  // 생성자
  constructor(
    private name: string,
    protected age: number,
    public position: string
  ) {
    this.name = name;
    this.age = age;
    this.position = position;
  }

  // 메서드
  work() {
    console.log(`${this.name} 일함`);
  }
}

생성자 매개변수에 name, age, position 처럼 접근 제어자가 설정되면 자동으로 필드도 함께 선언된다.

따라서 동일한 이름으로 필드를 중복 선언할 수 없게 된다.

class Employee {
  // 생성자
  constructor(
    private name: string,
    protected age: number,
    public position: string
  ) {
    this.name = name;
    this.age = age;
    this.position = position;
  }

  // 메서드
  work() {
    console.log(`${this.name} 일함`);
  }
}

또 다음과 접근 제어자가 설정된 매개변수들은 this.필드 = 매개변수가 자동으로 수행된다.

따라서 위 코드의 name, age, position은 모두 this 객체의 프로퍼티 값으로 자동 설정되기 때문에 다음과 같이 생성자 내부의 코드를 제거해도 된다.

class Employee {
  // 생성자
  constructor(
    private name: string,
    protected age: number,
    public position: string
  ) {}

  // 메서드
  work() {
    console.log(`${this.name} 일함`);
  }
}

인터페이스와 클래스

타입스크립트의 인터페이스는 클래스의 설계도 역할을 할 수 있다.
다음과 같이 인터페이스를 이용해 클래스에 어떤 필드들이 존재하고, 어떤 메서드가 존재하는지 정의할 수 있다.

인터페이스 CharacterInterfacename, moveSpeed 프로퍼티와 move메서드를 갖는 객체 타입을 정의한다. 그런데 이 인터페이스를 클래스에서 implements 키워드와 함께 사용하면 이제부터 이 클래스가 생성하는 객체는 모두 이 인터페이스 타입을 만족하도록 클래스를 구현해야 한다.

interface CharacterInterface {
  name: string;
  moveSpeed: number;
  move(): void;
}

class Character implements CharacterInterface {
  constructor(
    public name: string,
    public moveSpeed: number,
    private extra: string
  ) {}

  move(): void {
    console.log(`${this.moveSpeed} 속도로 이동!`);
  }
}

인터페이스로 선언하면 public만 선언할 수 있다.
왜냐하면 인터페이스는 겉으로 드러나는 약속이기 때문에 숨겨진(private) 것을 정의할 수 없기 때문이다.

profile
하고싶은거 짱많은 주니어 프론트엔드 개발자

0개의 댓글