본문 바로가기

Frontend/Typescript

인터페이스와 클래스

인터페이스

인터페이스는 타입 별칭과 구분할 수 있는데, 아직 타입 별칭에 대해서는 다루지 않았기 때문에 타입 별칭 부분에서 비교해보도록 하자.

/**
 * 인터페이스
 */

interface Person {
    readonly name: string;
    age?: number;
    sayHi(): void;
    sayHi(a: number, b: number): void;
}

const person: Person = {
    name: '이정환',
    sayHi: function () {
        console.log('Hi');
    }
}

인터페이스를 사용해서 타입을 정의하려면 위와 같이 사용하면 된다.

위 코드만 보면 타입 별칭과 특별히 차이가 없는 것처럼 느낄 수 있지만, 인터페이스에는 타입 별칭과 다른 특징이 있다,

그 부분은 아래에서 알아보도록 하자.

 

한편, 인터페이스에서 함수를 오버로딩하려면 위와 같이 호출 시그니처를 사용해야 한다.

 

인터페이스 확장하기

extends 키워드를 사용하면 인터페이스의 확장이 가능하다.

상속이라고 생각하면 된다.

 

자식 interface에서 재정의하려고 하는 경우, 해당 타입은 원본 타입의 서브 타입이어야만 한다.

아래에서 Animal이 타입으로 정의된 경우에도 인터페이스는 확장이 가능하다.

즉, 인터페이스는 객체 타입이면 확장이 가능하다.

/**
 * 인터페이스의 확장
 */

interface Animal {
    name: string;
    age: number;
}

interface Dog extends Animal {
    name: 'hello'  // string literal 타입으로 재정의
    isBark: boolean;
}

const dog: Dog = {
    name: 'hello',
    age: 12,
    isBark: true,
}

interface Cat extends Animal {
    isScratch: boolean;
}

interface Chicken extends Animal {
    isFly: boolean;
}

Dog 인터페이스는 Animal 인터페이스를 확장한다.

이때, name이라는 프로퍼티를 Dog 인터페이스에서 재정의하는데, Animal 인터페이스에서 name 프로퍼티는 string, Dog 인터페이스에서 name 프로퍼티는 'hello'(string literal 타입)이므로 원본 타입의 서브 타입이다.

따라서, 문제 없이 잘 동작한다.

 

/**
 * 인터페이스의 다중 확장
 */

interface Animal {
    name: string;
    age: number;
}

interface Dog extends Animal {
    isBark: boolean;
}

interface Cat extends Animal {
    isScratch: boolean;
}

interface DogCat extends Dog, Cat {
    
}

const dogCat: DogCat = {
    name: '',
    age: 12,
    isBark: true,
    isScratch: true,
}

 

인터페이스 합치기

인터페이스를 여러 번 선언한 경우, 해당 프로퍼티들이 합쳐진다.

같은 이름의 프로퍼티를 중복 선언하는 경우 타입이 동일해야 한다.

/**
 * 선언 합치기
 */

interface Person {
    name: string;
}

interface Person {
    name: number;  // 충돌
                   // 후속 속성 선언에 같은 형식이 있어야 합니다.
                   //'name' 속성이 'string' 형식이어야 하는데 여기에는 'number' 형식이 있습니다.
    age: number;
}

const person: Person = {
    name: '',
    age: 23,
}

 

라이브러리 타입 정의에 우리가 원하는 타입을 추가해주고 싶은 경우에 많이 사용한다.

이를 모듈 보강이라고 한다.

/**
 * 모듈 보강
 */

interface Lib {
    a: number;
    b: number;
}

interface Lib {
    c: number;
}

const lib: Lib = {
    a: 1,
    b: 2,
    c: 3,
};

 

클래스

/**
 * 클래스
 */

class Student {
    // 필드
    name: string;
    grade: string;
    age: number;

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

    // 메서드
    study() {
        console.log('열심히 공부한다.');
    }
    introduce() {
        console.log(`안녕하세요! ${this.name}입니다.`);
    }
}

class StudentDeveloper extends Student {
    // 필드
    skill: string;

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

    // 메서드
    programming() {
        console.log(`${this.skill}로 프로그래밍한다.`)
    }
}

// Student 인스턴스
let studentA = new Student('이정환', 'A+', 27);
studentA.study();
studentA.introduce();

const studentB: Student = {
    name: '이정환',
    grade: 'A+',
    age: 27,
    study() {

    },
    introduce() {
        
    }
}

클래스의 상속을 하는 경우, 자식 클래스의 생성자에서 부모 클래스의 생성자를 불러서 부모 클래스에 존재하던 필드 값을 초기화해주어야 한다.

타입스크립트에서의 클래스는 자바스크립트의 클래스로 취급되면서 타입으로도 취급된다.

그래서 studentB와 같이 나타낼 수 있는 것이다.

이는 타입스크립트가 구조적 타입 시스템을 따르기 때문이다.

 

접근 제어자

/**
 * 접근 제어자
 * access modifier
 * => public private protected
 */

class Employee {
    constructor(private name: string, protected age: number, private position: string) {}

    work() {
        console.log('일한다.');
    }
}

const employee = new Employee('이정환', 27, 'developer');

접근 제어자는 public, protected, private이 있다.

클래스의 필드에 접근 제어자를 명시해주지 않는 한, 기본적으로 public으로 설정되어 있다.

public은 별도의 접근 제한을 하지 않는다.

필드가 private으로 설정되어 있는 경우, 클래스 외부(파생 클래스 포함)에서는 접근하지 못하고, 클래스 내부에서만 접근이 가능하다.

그래서 클래스 내부 메서드에서는 접근이 가능하다.

다만 파생 클래스 내부 메서드에서는 접근이 불가능하다.

반면, 접근 제어자를 protected로 설정해주면, 외부에서는 접근이 불가능하지만, 파생 클래스에서는 접근이 가능하다.

 

또한, 생성자의 매개변수에 접근 제어자를 설정해주는 경우, 필드도 자동으로 생성해주고, 값의 초기화도 진행해준다.

 

인터페이스와 클래스

/**
 * 인터페이스와 클래스
 */

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} 속도로 이동`);
    }
}

implements 키워드를 사용하면 클래스가 인터페이스 내부 프로퍼티를 받아와서 구현할 수 있다.

인터페이스는 무조건 public 필드만 정의할 수 있다.

private 필드가 필요한 경우, 따로 클래스 내부에 정의해주어야 한다.

'Frontend > Typescript' 카테고리의 다른 글

타입 조작하기  (0) 2023.06.23
제네릭  (0) 2023.06.20
함수와 타입  (0) 2023.06.20
타입스크립트 이해하기  (0) 2023.06.20
타입스크립트 기본  (0) 2023.06.20