타입스크립트 고급 타입 정의 방법 / 인터페이스, 클래스, 제네릭 타입 등

인터페이스

인터페이스는 클래스가 구현해야 할 속성 및 메서드 구조를 정의하는 설계도 역할입니다.

인터페이스 타입 정의

// 인터페이스로 객체 타입 정의
interface 인터페이스명 {
  프로퍼티명1: string;
  프로퍼티명2: number;
}

// 인터페이스 타입에 맞는 객체 정의
const 변수명: 인터페이스명 = {
  프로퍼티명1: "한서윤",
  프로퍼티명2: 25
}

인터페이스로 객체 타입 정의 시 상속, 선언 병합 기능을 이용할 수 있습니다.

인터페이스 정의 시 함수 프로퍼티 추가

// 인터페이스로 객체 타입 정의
interface 인터페이스명 {
  프로퍼티명: () => void; // 함수 타입 표현식으로 함수 프로퍼티 선언 가능 (1개만 선언 가능. 오버로드 불가)
  또는
  프로퍼티명(): void; // 함수 호출 시그니처 형태로 함수 프로퍼티 선언 가능
  프로퍼티명(매개변수명: number): void; // 함수 호출 시그니처 문법 이용 시 여러 오버로드 시그니처 정의 가능
}

// 인터페이스 타입에 맞는 객체 정의
const 변수명: 인터페이스명 = {
  프로퍼티명: function () {
    console.log("Hi");
  }
};

함수 프로퍼티를 갖는 인터페이스 정의할 수 있습니다.

인터페이스 상속 (extends)

// 부모 인터페이스 타입 정의
interface Person {
  name: string;
}
또는
// 부모 객체 타입 정의
type Person = {
  name: string;
}

// 자식 인터페이스 타입 정의
// 부모 인터페이스 타입 또는 부모 객체 타입 프로퍼티 상속
interface Student extends Person {
  grade: number;
}
interface Developer extends Person {
  name: "개발자"; // 스트링 리터럴 타입 (부모 타입 string의 서브 타입)
  grade: number;
}

부모 인터페이스 타입 프로퍼티들을 상속한 자식 인터페이스 타입을 정의할 수 있습니다.
상속을 이용하면 자식 타입 정의 시 중복 속성 코드 작성을 최소화할 수 있습니다.
부모에서 상속받은 프로퍼티 재정의 시, 원본 타입의 서브타입이어야 합니다.

인터페이스 다중 상속

interface 자식인터페이스명 extends 부모인터페이스명1, 부모인터페이스명2 {
  // 프로퍼티 정의
}

const 변수명: 자식인터페이스명 = {
  // 모든 부모, 자식 필수 프로퍼티 값 정의 필요
}

여러 부모 인터페이스 타입 프로퍼티들을 모두 갖는 자식 인터페이스 타입을 정의할 수 있습니다.

인터페이스 선언 병합

interface User {
  name: string;
}

interface User {
  age: number;
}

// 인터페이스 선언 병합 결과
interface User {
  name: string;
  age: number;
}

동일한 인터페이스명으로 인터페이스 타입들을 선언하면, 모든 프로퍼티들이 병합됩니다.
각 인터페이스에 같은 프로퍼티 정의 시에는 반드시 타입이 같아야 합니다.


클래스

클래스는 동일한 속성과 기능을 가진 여러 객체를 생성할 때 설계도 역할을 합니다.
클래스 활용 시 객체 코드 중복을 최소화 할 수 있습니다.

클래스 타입 정의

class 클래스명 {
  필드명1: number;
  필드명2: string = ""; // 기본값 설정
  필드명3?: boolean; // 선택적 프로퍼티

  constructor(필드명1: number, 필드명2: string, 필드명3: boolean) {
    this.필드명1 = 필드명1;
    this.필드명2 = 필드명2;
    this.필드명3 = 필드명3;
  }

  메서드명() {
    
  }
}

각 필드 타입이 명시된 클래스를 정의할 수 있습니다.
타입스크립트 클래스는 타입으로 사용되어 변수 타입 안정성을 높일 수 있습니다.

Typescript 클래스 상속 방법

class 자식클래스명 extends 부모클래스명 {
  필드명4: number;

  constructor(필드명1: number, 필드명2: string, 필드명3: boolean, 필드명4: number) {
    super(필드명1, 필드명2, 필드명3);
    this.필드명4 = 필드명4;
  }
}

생성자에서 부모 클래스 생성자를 호출하는 자식 클래스를 정의할 수 있습니다.

객체 인스턴스 생성

// 클래스 생성자를 이용한 객체 인스턴스 생성
const 변수명 = new 클래스명(필드1값, 필드2값, 필드3값);

// 객체 정보 조회
console.log(변수명);

// 객체 필드 값 수정
변수명.필드명1 = 값;

// 객체 내 메서드 호출
변수명.메서드명();

타입스크립트 클래스를 이용하여 객체 인스턴스를 생성할 수 있습니다.

클래스 타입에 맞춘 일반 객체 정의

const 변수명: 클래스명 = {
  필드명1: 필드1값,
  필드명2: 필드2값,
  필드명3: 필드3값,
  메서드명() { }
}

클래스를 타입처럼 사용하여 객체를 정의할 수 있습니다.
인스턴스가 생성되는 문법은 아닙니다.

클래스 접근제어자

class 클래스명 {
  // 어디에서든 접근 가능 (기본값)
  public 필드명1;
  필드명2; // 접근제어자 생략 시 public

  // 현재 클래스에서만 접근 가능
  // 외부 및 자식 클래스에서 필드 접근 불가
  // 메서드를 통해서만 필드 접근 가능
  private 필드명3;

  // 현재 클래스 또는 자식 클래스에서만 필드 접근 가능
  // java와 달리, 같은 폴더/파일에서는 접근 불가
  protected 필드명4;

  public 메서드명() {
    // 클래스 내부에서는 private 필드 접근 가능
    return this.필드명3;
  }
}

타입스크립트 접근제어자 사용 예시입니다.
Javascript에는 접근제어자가 없고, 타입스크립트에서만 제공하는 기능입니다.

클래스 생성자에서 접근제어자 사용

class 클래스명 {
  constructor(public 필드명1: string, private 필드명2: number) {
    // 필드 값 초기화 로직 생략 가능
  }
}

위와 같이, 클래스 생성자에서 각 필드에 접근제어자 사용 시 필드를 정의하지 않아도 됩니다.
필드에 값을 할당하는 로직도 타입스크립트가 Javascript 파일로 컴파일 시 자동으로 생성해 줍니다.

인터페이스를 구현한 클래스 정의

// 인터페이스 정의
interface 인터페이스명 {
  필드명1: string; // 인터페이스에서는 public 필드만 정의 가능
  필드명2: number;
  메서드명(): void;
}

// 인터페이스를 구현한 클래스 정의
// 인터페이스 내 필드 및 메서드 미구현 시 오류 발생
class 클래스명 implements 인터페이스명 {
  필드명1: string;
  필드명2: number;
  private 필드명3: string;

  constructor(필드명1: string, 필드명2: number, 필드명3: string) {
    this.필드명1 = 필드명1;
    this.필드명2 = 필드명2;
    this.필드명3 = 필드명3;
  }

  메서드명(): void {
    console.log(`필드명1 : ${this.필드명1}`);
  }
}

인터페이스 정의 후, 인터페이스를 구현한 클래스를 정의할 수 있습니다.


제네릭

작성예정

제네릭 타입 정의

작성예정