본문 바로가기

TypeScript

인터페이스 (Interface)

인터페이스가 무엇인지 아래의 간단한 예시 코드와 함께 살펴보도록 하겠습니다.


예시 1

interface User {
  name: string;
  age: number;
  isValid: boolean;
}


User는 앞에 interface 키워드를 사용하고 있으며, 뒤에 중괄호 안에서 속성의 타입이 명시되어 있습니다.

이렇게 인터페이스를 만들 때는 interface 키워드를 사용하고 인터페이스의 이름을 지정해주면 되는데, 이름의 첫 글자는 대문자로 만들어주시면 되고 객체 데이터의 속성 이름과 각 속성에 대한 타입을 명시해주면 됩니다.

이렇게 만들어진 인터페이스는 이름이 지정되어 있기 때문에 재사용이 가능하며 중괄호 사이에서 각각의 속성에 대한 타입을 명시해서 사용이 가능합니다.

const rati: User = {
  name: "rati",
  age: 5,
  isValid: true,
};

const merry: User = { // 타입 에러 발생
  name: "merry",
  age: 3,
  // 인터페이스 User에 명시된 isValid 속성이 있고, 타입도 일치해야 타입 에러가 사라집니다.
};


인터페이스는 변수의 타입으로 사용할 수 있게 되고, 해당 타입으로 명시된 변수는 인터페이스와 동일한 구조여야 하며, 속성의 타입 또한 일치하는 타입의 값을 가지고 있어야 합니다.

인터페이스 User를 사용하여 타입을 명시했기 때문에 변수 rati는 할당받는 객체의 속성에 name, age, isValid 속성이 무조건 있어야 하며, 각각의 속성의 타입 또한 인터페이스 User에서 명시한 것과 일치해야 합니다.

변수 merry는 인터페이스 User에서 명시한 isvalid 속성이 없기 때문에 타입 에러가 발생하게 됩니다.



선택적 속성 - ?

interface User {
  name: string;
  age: number;
  isValid?: boolean;
}

const merry: User = {
  name: "merry",
  age: 3,
};


속성 뒤에 ?를 붙여주게 되면 해당 속성은 선택적 속성이 됩니다. 즉, 해당 속성이 있을 수도 있고 없을 수도 있다고 명시해주는 것입니다.
이제 인터페이스 User는 isValid 속성이 있을 수도 있고 없을 수도 있다 명시했기 때문에 변수 merry에서 발생한 타입 에러가 사라지게 됩니다.



읽기 전용 속성 - readonly

interface User {
  name: string;
  readonly age: number; // 읽기 전용 속성
  isValid?: boolean;
}


속성 앞에 readonly를 사용하게 되면 해당 속성에는 더 이상 값을 할당할 수 없고, 읽기만 가능하게 됩니다.

const rati: User = {
  name: "rati",
  age: 5,
  isValid: true,
};

rati.age = 6; // 타입 에러 발생

인터페이스 User를 사용하여 타입을 명시했기 때문에 변수 rati에 할당된 객체의 age 속성은 읽기 전용 속성이 되었기 때문에 더 이상 값을 할당할 수 없으며, 값을 할당할 시 위의 타입 에러가 발생하게 됩니다.



함수 타입 - 호출 시그니처( Call Signature )


interface GetName {}

interface User {
  name: string;
  age: number;
  getName: GetName;
}

const rati: User = {
  name: "rati",
  age: 5,
  getName(message: string) {
    console.log(message);
    return this.name;
  },
};

rati.getName("hello"); // 타입 에러 발생


인터페이스 GetName의 내용은 비워져 있는 상태이며, 인터페이스 User는 객체 데이터를 지정하고 있고, 객체 내부의 각각의 속성의 타입을 위와 같이 명시하고 있습니다. getName 속성에서는 다른 인터페이스를 사용하여 타입을 명시하고 있습니다.

*다른 인터페이스를 인터페이스 내부에서 사용이 가능합니다.

변수 rati는 객체를 할당받고 있으며 인터페이스 User를 사용하여 타입을 명시하고 있으며, getName 속성에는 함수가 할당되어 있고 해당 함수의 매개 변수 message는 인수로 문자 데이터를 전달받는다고 명시되어 있습니다.

getName 속성의 함수에 인수로 문자 데이터를 전달하게 되면 위의 타입 에러 메시지를 확인할 수 있습니다. 해당 에러 메시지에 있는 호출 시그니처는 하나의 함수 타입을 의미합니다.

즉, getName은 함수 타입인데, User 인터페이스에서 속성 getName은 인터페이스GetName를 사용하여 타입이 명시되어 있기 때문에, 인터페이스 GetName에는 함수 타입을 만들어 주어야 합니다. 이때 호출 시그니처라는 개념을 통해 인터페이스의 내용이 작성되어야 합니다.

interface GetName {
  (message : string) : string
}


함수 타입을 만들기 위해서는 인터페이스 내부에 key : value 형식으로 내용을 작성해주고 key에는 소괄호 안에 매개변수의 타입, value에는 반환하는 값의 타입을 명시해 줍니다. 이렇게 인터페이스 내부에서 소괄호 안에 내용을 작성하는 것을 호출 시그니처라고 합니다.

이렇게 호출 시그니처를 통해서 함수의 타입을 명시해줄 수 있게 됩니다.


인터페이스를 통해 함수의 타입을 만드는 이유


인터페이스의 경우 위와 같이 GetName, User 등의 이름을 지정할 수 있습니다. 이름이 있다는 것은 해당 이름을 통해 재사용이 가능함을 의미합니다.

interface User {
  name: string;
  age: number;
  getName: (message: string) => string;
}


만약 인터페이스 GetName의 내용을 딱 한 번만 사용한다면 별도의 인터페이스를 만들 필요 없이 위와 같이 속성에 바로 명시해줄 수 있지만, 재사용을 한다면 인터페이스에서 호출 시그니처를 사용하여 함수의 타입을 만들어 재사용할 수 있습니다.



인덱싱 가능 타입 - 인덱스 시그니처 ( Index Signature )



인덱스 가능 타입은 대표적으로 배열이나 객체에 타입을 지정할 수 있는 인터페이스 방식입니다.

배열

interface Fruits {
  [item: number]: string;
}

const fruits: Fruits = ["apple", "banana", "cherry"];


인터페이스 Fruits 내부에서 호출 시그니처와 비슷한 방식으로 대괄호 안에서 내용을 작성하여 타입을 명시해주고 있습니다. 이렇게 대괄호에 내용을 작성하는 것을 인덱스 시그니처라고 합니다.

인덱스 시그니처를 사용하여 배열 안의 요소는 숫자여야 한다고 타입을 명시하고, 대괄호를 통해 하나의 속성이 만들어지면 속성의 값으로는 문자 타입의 데이터가 와야 한다고 명시하고 있습니다. 아래의 콘솔 이미지와 함께 인덱스 시그니처에 대해 조금 더 살펴보도록 하겠습니다.

인터페이스 Fruits 내부에서 인덱스 시그니처를 통해 타입을 명시해주고 있으며, key : value 형식으로 타입을 명시해주고 있습니다.

인터페이스 Fruits를 사용하여 타입을 명시한 변수 fruits를 콘솔에 출력하게 되면 위의 결과를 확인할 수 있으며, 결과로 나온 배열은
key : value 형식으로 0번 인덱스에 "apple", 1번 인덱스에 "banana", 2번 인덱스에 "cherry"가 있는 것을 확인할 수 있습니다.

즉, 인덱스 시그니처를 사용하여 key : value 형식의 타입 명시는 key는 배열의 인덱스, value는 배열의 요소의 타입을 명시해주는 것입니다. 인덱스는 숫자 데이터임으로 대괄호에 number 타입을 명시해 주고, 배열의 각 요소는 문자 데이터임으로 string 타입을 명시해준 것입니다.


객체

interface User {
  [key: string]: unknown;
  name: string;
  age: number;
}

const rati: User = {
  name: "rati",
  age: 5,
};


인터페이스 User 내부에는 인덱스 시그니처로 위와 같이 작성되어 있습니다. 배열에서의 인덱스 시그니처와 동일하게 객체를 콘솔로 출력하면 key : value 형식으로 key, 즉 속성의 이름이 오기 때문에 string 타입이라고 명시해주고 있고, 속성의 값으로 어떤 타입이 올지 알 수 없음을 뜻하는 unknown 타입을 명시해주고 있습니다.

추가로 속성 name, age는 값의 타입이 명확하게 명시되어 있기 때문에 name, age를 제외한 속성은 값으로 정확이 어떤 타입이 올지 알 수 없음을 의미하고 있습니다.

이렇게 만들어진 인터페이스 User를 변수 rati에 사용하여 타입을 명시해주고 있으며, 현재까지는 인덱스 시그니처와 상관없이 정상적으로 동작하고 있습니다.

rati["isValid"] = true;
rati["emails"] = ["exex@email.com", "test@email.com"];


추가로 위와 같이 객체를 할당받은 변수 rati에 대괄호 표기법을 사용하여 isValid 속성을 추가하고, 속성의 값으로 true를 추가해 주었으며
email일도 동일하게 추가해 주었습니다.

이것이 인터페이스에서 인덱스 시그니처를 사용한 이유이며 객체에 대괄호 표기법을 사용하여 인덱싱하여 값을 할당하는 것이 가능하다고 인터페이스 내부에 인덱스 시그니처를 사용하여 명시한 것입니다.

*인덱싱 시그니처를 사용하지 않으면 위의 예시처럼 인터페이스에 명시되지 않은 속성을 추가할 수 없습니다.

만약 인터페이스에서 인덱스 시그니처를 제거하게 되면 타입 에러가 발생합니다.

이는 인터페이스 User에서 인덱스 형식을 사용할 수 없기 때문이며 객체 rati는 인터페이스 User에 명시되지 않은 속성을 추가하게 되면 타입에러가 발생하기 때문입니다.




상속


예시 1

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

interface UserB extends UserA {
  isValid: boolean;
}


인터페이스 UserA에는 name, age 속성이 명시되어 있고 속성에 대한 값의 타입이 명시되어 있으며, 인터페이스 UserB는 extends 키워드를 사용하고 있고, extends 키워드 뒤에는 인터페이스 UserA가 사용되고 있습니다.

인터페이스에서 사용되는 extends는 클래스와 동일하게 확장이라는 의미를 가집니다. 즉, 인터페이스 UserA의 내용을 상속받아서 추가로 내용을 작성할 수 있게 됩니다.

interface UserB {
  name: string;
  age: number;
  isValid: boolean;
}


인터페이스 UserB는 인터페이스 UserA를 상속받고 isValid 속성에 대한 내용이 작성되어 있기 때문에 내부에 위와 같이 작성되어 있는 것과 동일하게 작동합니다.

const rati: UserA = {
  name: "rati",
  age: 5,
  isValid: true, // 타입 에러 발생
};

const merry: UserB = {
  name: "merry",
  age: 3,
  isValid: true,
};

변수 rati에는 인터페이스 UserA를 사용하여 타입을 명시하고 있으며, 인터페이스 UserA는 name, age 속성에 대해서만 명시되어 있기 때문에 isValid 속성을 추가하여 사용할 수 없어 타입 에러가 발생하게 됩니다.

변수 merry에는 인터페이스 UserB를 사용하여 타입을 명시하고 있으며, UserB의 경우 UserA의 내용을 상송받아 isValid 속성에 대해 명시해주었기 때문에 정상적으로 작동하게 됩니다.


예시 2

interface FullName {
  firstName: string;
  lastName: string;
}

interface FullName {
  middlename: string;
  lastName: boolean; // 타입 에러 발생
}

인터페이스 FullName에 firstName, lastName 속성에 대해 명시되어 있으며, 아래에서 같은 이름의 인터페이스에서 lastName 속성에 boolean 타입을 명시해주고 있습니다.


타입 에러 메시지를 확인해보면 후속 속성 선언에 같은 형식이 있어야 한다는 메시지를 확인할 수 있으며, 아래와 같은 결과를 정리할 수 있습니다.

  • 동일한 이름의 인터페이스를 만들 수 있습니다.
  • 동일한 이름의 후속 인터페이스(나중에 만들어진 인터페이스)에 속성에 대한 내용을 추가로 명시해줄 수 있습니다.
  • 후속 인터페이스에서 존재하는 속성을 명시할 때는 동일한 타입을 명시해주어야 합니다.

interface FullName {
  firstName: string;
  lastName: string;
}

interface FullName {
  middlename: string;
  lastName: string;
}

const fullName: FullName = {
  firstName: "tomas",
  middlename: "sean",
  lastName: "connery",
};


변수 fullName에 인터페이스 FullName을 사용하여 타입을 명시해주게 되며 정상적으로 작동하게 됩니다. 이는 전속 인터페이스의 내용과 후속 인터페이스 내용이 합쳐져 결과적으로 인터페이스 FullName는 firstName, middleName, lastName의 속성에 대한 내용이 명시되어 있는 것이기 때문입니다.

'TypeScript' 카테고리의 다른 글

함수  (0) 2023.01.02
타입 별칭(Alias)  (0) 2023.01.02
타입 가드(Guard)  (0) 2023.01.02
타입 단언과 할당 단언  (0) 2023.01.02
타입 추론 (Inference)  (2) 2023.01.01