인터페이스

가은·2023년 7월 18일
0

타입스크립트

목록 보기
2/4

TypeScript에서 interface는 객체의 구조를 말한다. → 객체의 형태를 설명하는 방법

클래스와 달리 인터페이스는 청사진으로 이용하지 않고 사용자 정의 타입으로만 사용하게 된다.

인터페이스를 정의할 때도 첫글자는 대문자로 작성한다.

function printLabel(labelObj: { label: string }) {
  console.log(labelObj.label);
}

let myObj = { size: 10, label: "크가가 10인 객체" };
printLabel(myObj);

myObjprintLabel 보다 더 많은 프로퍼티를 갖고 있지만, 컴파일러는 최소한 필요한 프로퍼티가 있는지와 타입이 잘 맞는지만 검사한다.

위 코드를 인터페이스를 사용하면 아래와 같다.

interface LabeledValue {
  label: string;
}

function printLabel(labelObj: LabeledValue) {
  console.log(labelObj.label);
}

let myObj = { size: 10, label: "크가가 10인 객체" };
printLabel(myObj);

선택적 프로퍼티

인터페이스의 프로퍼티는 어떤 조건에서만 존재하거나 아예 없을 수도 있다.

선택적 프로퍼티들은 객체 안의 몇 개의 프로퍼티만 채워 함수를 전달하는 “option bags” 패턴을 만들 때 유용하다.

선택적 프로퍼티를 가지는 인터페이스는 다른 인터페이스와 비슷하게 작성되고, 선택적 프로퍼티는 선언에서 프로퍼티 이름 끝에 ? 를 붙여 표시한다.

  • 선택적 프로퍼티의 이점은 인터페이스에 속하지 않는 프로퍼티의 사용을 방지하면서, 사용 가능한 속성을 기술하는 것이다. (프로퍼티 이름이 오타나면 알아차리기 쉽다.)
interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
  let newSquare = { color: "white", area: 100 };
  if (config.color) {
    newSquare.color = config.color;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({ color: "black" });

읽기전용 프로퍼티

일부 프로퍼티들은 객체가 처음 생성될 때만 수정 가능해야한다.

프로퍼티 이름 앞에 readonly 를 넣어서 지정할 수 있다.

interface Point {
  readonly x: number;
  readonly y: number;
}

let p1: Point = { x: 10, y: 20 };
// p1.x = 5; -> error

TypeScript에서는 모든 변경 메서드가 제거된 Array<T> 와 동일한 ReadonlyArray<T> 타입이 제공된다.

→ 생성 후에 배열을 변경하지 않음을 보장할 수 있다.

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
// ro[0] = 12;
// ro.push(5);
// a = ro;
// => error

a = ro; 를 보면 ReadonlyArray 를 일반 배열에 재할당이 불가능한 것을 확인할 수 있다.

타입 단언으로 오버라이드 하는 것은 가능하다.

a = ro as number[];

💡 readonly 는 프로퍼티에 사용하고 const 는 변수에 사용한다.

초과 프로퍼티 검사

객체 리터럴은 다른 변수에 할당할 때나 인수로 전달할 때, 초과 프로퍼티 검사를 받는다.

요구하는 인터페이스에 명시되지 않는 프로퍼티를 가지고 있으면 에러가 발생한다.

→ 똑같은 객체를 변수에 할당한 뒤 전달하면 에러가 발생하지 않는다.

→ 타입 단언 as 문법을 이용하여 초과 프로퍼티 검사를 피할 수도 있다.

let mySquare = createSquare({ width:100, opacity: 0.5 } as SquareConfig);

특별한 경우에, 추가 프로퍼티가 있음이 확실하다면, 문자열 인덱스 서명을 추가하는 것이 더 나은 방법이다.

문자열 인덱스 서명

color와 width 프로퍼티 외의 다른 프로퍼티를 가질 수 있다면 다음과 같이 정의할 수 있다.

interface SquareConfig {
  color?: string;
  width?: number;
  **[propName: string]: any;**
	// 정확한 속성 이름/개수는 모르지만 
	// 문자열로 해석 가능한 속성 이름을 지녀야하고 
	// 해당 속성 값의 타입은 아무거나 올 수 있음
}

인덱스 서명의 타입은 string 또는 number 여야 한다.

⚠️ 인덱스로 할당할 값의 타입이 any 가 아닌 경우, 인터페이스에 명시된 모든 프로퍼티는 모두 해당 타입을 따라야 한다.

⚠️ 또한 선택적 프로퍼티를 사용한 경우, string | undefined 타입을 인덱스 키의 타입인 string 에 할당할 수 없다는 에러가 발생한다.

interface Person {
    // name?: string; 에러
    // age: number; 에러
    favorite: string;
    [propname: string]: string;
}

함수 타입

인터페이스는 객체 외에 함수 타입을 설명할 수 있다.

인터페이스로 함수 타입을 기술하기 위해, 인터페이스에 호출 서명(call signature)를 전달한다.

이는 매개변수 목록과 반환 타입만 주어진 함수 선언과 비슷하다.

→ 각 매개변수는 이름과 타입 모두 필요하다.

interface SearchFunc {
  (source: string, subString: string): boolean;
}

한번 정의되면, 함수 타입 인터페이스는 다른 인터페이스처럼 사용할 수 있다.

let mySearch: SearchFunc;
// 매개변수의 이름이 정의한 interface와 같을 필요는 없다.
mySearch = function (source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
};

함수 매개변수들은 같은 위치에 대응되는 매개변수끼리 한번에 하나씩 검사한다.

만약 타입을 지정하고 싶지 않다면, SearchFunc 타입의 변수로 직접 함수 값이 할당되었기 때문에 TypeScript의 문맥상 타이핑(contextual typing)이 인수 타입을 추론할 수 있다.

함수 표현식이 boolean 이 아닌 다른 타입을 반환하면, SearchFunc 인터페이스에 정의된 반환 타입과 일치하지 않는다는 에러를 발생시킨다.

Indexable Types

타입을 인덱스로 기술할 수 있다.

인덱서블 타입은 인덱싱 할 때 해당 반환 유형과 함께 객체를 인덱싱하는 데 사용할 수 있는 타입을 기술하는 인덱스 시그니처를 가지고 있다.

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["boo", "foo"];

let myStr: string = myArray[0];

StringArray 의 인덱스 서명은 StringArraynumber 로 색인화(indexed)되면 string 을 반환할 것을 나타낸다.

  • 인덱스 서명은 문자열과 숫자 타입만 지원한다.

두 타입의 인덱서(indexer)를 모두 지원하는 것은 가능하지만, 숫자 인덱서에서 반환된 타입은 반드시 문자열 인덱서에서 반환된 타입의 하위 타입이어야 한다.

number 로 인덱싱 할 때, JavaScript는 실제로 객체를 인덱싱하기 전에 string 으로 변환하기 때문이다.

→ 100(number)로 인덱싱 하는 것은 “100”(string)로 인덱싱하는 것과 같기 때문에, 일관성이 있어야한다.

interface AnimalEx {
  name: string;
}

interface Dog extends AnimalEx {
  breed: string;
}

interface NotOk {
  // [x: number]: AnimalEx; -> error 숫자형 문자열로 인덱싱을 하면 다른 타입의 AnimalEx를 얻게 될 것 입니다.
  [x: string]: Dog;
}

문자열 인덱스 시그니처는 “사전” 패턴을 기술하는데 강력하지만, 모든 프로퍼티들이 반환 타입과 일치하도록 강제한다.

interface Number {
  [index: string]: number;
  length: number;
  // name: string; -> error 'name'의 타입은 인덱서의 하위 타입이 아님
}

인덱스 시그니처가 프로퍼티 타입들의 합집합이라면 다른 타입의 프로퍼티들도 허용할 수 있다.

interface NumberOrString {
  [index: string]: number | string;
  length: number;
  name: string;
}

인덱스의 할당을 막기 위해 인덱스 시그니처를 “읽기 전용”으로 만들 수 있다.

interface ReadonlyStringArr {
  readonly [index: number]: string;
}

let myArr: ReadonlyStringArr = ["boo", "foo"];
// myArr[2] = "hi"; -> error

Class Types

인터페이스 구현

interface ClockInterface {
  currentTime: Date;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  constructor(h: number, m: number) {}
}

setTime 처럼 클래스에 구현된 메서드를 인터페이스 안에서도 기술할 수 있다.

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {}
  • 인터페이스는 클래스의 public과 private 모두보다, pulic을 기술한다. 그래서 클래스 인스턴스의 private에서는 특정 타입이 있는지 검사할 수 없다.

인터페이스 확장

인터페이스 확장은 인터페이스의 멤버를 다른 인터페이스에 복사하는 것을 가능하게 해주는데, 인터페이스를 재사용성 높은 컴포넌트로 쪼갤 때, 유연함을 제공해준다.

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLenth: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLenth = 10;

인터페이스는 여러 인터페이스를 확장할 수 있어, 모든 인터페이스의 조합을 만들 수 있다.

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLenth: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLenth = 10;
square.penWidth = 5.0;

❓ type 정의(사용자 정의 타입)과 다른 점

  • 인터페이스는 객체의 구조를 설명하기 위해서만 사용된다. → 클래스 사용 시 인터페이스 구조에 맞춰서 구현해야 한다.
  • 인터페이스는 주로 구체적인 구현이 아닌 서로 다른 클래스 간의 기능을 공유하기 위해 사용한다.

선언 방법

interface Interface {
  // 프로퍼티 선언
  propertyName: type;

  // 메서드 선언
  methodName(parameter: type): returnType;

  // 선택적 프로퍼티 
  optionalProperty?: type;

  // 읽기 전용 프로퍼티
  readonly readonlyProperty: type;
}

interface FnInterface {
	// 함수 선언
  (x: type, y: type): returnType;
}
profile
일이 재밌게 진행 되겠는걸?

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

아주 유익한 내용이네요!

답글 달기