TIL 73 | TypeScript Interface

이재승님의 실전 리액트 프로그래밍을 읽고 정리한 내용입니다.

인터페이스

자바에서 인터페이스는 클래스를 구현하기 전에 필요한 메서드를 정의하는 용도로 쓰인다. 타입스크립트에서는 좀 더 다양한 것들을 정의하는데 사용할 수 있다.

1. 객체 타입 정의하기

인터페이스로 타입을 정의할 때는 interface 키워드를 사용한다.

interface Person {
  name: string;
  age: number;
}
const p1: Person = { name: 'Mia', age: 23};
const p2: Person = { name: 'Dan', age: 'twelve'}; // 타입 에러

선택 속성

선택 속성은 객체에서 없어도 되는 속성을 말한다. 앞서 배웠듯이 선택 속성은 물음표 기호를 사용한다.

interface Person {
  name: string;
  age?: number;
}
const p1: Person = {name: 'ryan'};

물음표 기호를 사용하선택 지 않고 undefined를 유니온 타입으로 추가하면 선택 속성과 달리 명시적으로 age 속성을 입력해야 한다.

interface Person {
  name: string;
  age: number | undefined;
}
const p1: Person = {name: 'ryan'}; // 타입 에러
const p2: Person = {name: 'ryan', age: undefined};

읽기 전용 속성

객체에서 읽기 전용 속성은 값이 변하지 않는 속성을 말한다. readonly키워드를 사용한다. 아래와 같이 변수를 정의하는 시점에는 값을 할당할 수 있지만 값을 수정하려고 하면 컴파일 에러가 발생한다.

interface Person {
  readonly name: string;
  age?: number;
}
const p1: Person = {name: 'ryan'};
p1.name = 'apeach'; // 컴파일 에러

정의되지 않은 속성값 처리

보통은 객체가 인터페이스에 정의되지 않은 속성값을 가지고 있어도 할당이 가능하다. 단, 리터럴로 값을 초기화하는 경우 인터페이스에 정의되지 않은 속성값이 있으면 타입 에러가 발생한다.

interface Person {
  readonly name: string;
  age?: number;
}
const p1: Person = {
  name: 'mia';
  birthday: '1995-08-01'; // 타입 에러
};
const p2 = {
  name: 'david';
  birthday: '1997-04-14';
};
const p3: Person = p2;

Person 인터페이스에 정의되지 않은 속성을 리터럴로 입력하면 타입 에러가 일어난 것을 볼 수 있다. p2는 Person 인터페이스에 정의되지 않은 속성을 포함하지만 p3 타입이 p2 타입을 포함하는 더 큰 타입이기 때문에 타입 에러가 발생하지 않는다.

2. 인덱스 타입 정의하기

인터페이스에서 이름을 구체적으로 정의하지 않고 값의 타입만 정의하는 것을 인덱스 타입이라고 한다.

interface Person {
  readonly name: string;
  age: number;
  [key: string]: string | number;
}
const p1: Person = {
  name: 'mike',
  birthday: '1997-01-01',
  age: '25', // 타입 에러
};

age는 명시적으로 숫자로 정의했기 때문에 타입 에러가 발생한다.

여러 개의 인덱스를 정의하는 경우

자바스크립트에서는 속성 이름에 숫자와 문자열을 사용할 수 있고, 속성 이름에 숫자를 사용하면 문자열로 변환된 후 사용된다. 따라서 타입스크립트에서는 숫자인 속성 이름의 값이 문자열로 할당 가능한지 검사한다.

interface YearPriceMap {
  [year: number]: A;
  [year: string]: B;
}

속성 이름이 숫자인 A 타입은 B 타입에 할당 가능해야 한다.

interface YearPriceMap {
  [year: number]: number;
  [year: string]: string | number;
}

const yearMap: YearPriceMap = {};
yearMap[1998] = 1000;
yearMap[1998] = 'abc'; // 타입 에러
yearMap['2000'] = 1234;
yearMap['2000'] = 'million';

인터페이스로 할 수 있는 것들

인터페이스로 함수 타입 정의하기

// 함수 타입을 인터페이스로 정의하기
interface GetInfo {
  (name: string, age: number): string;
}
const getInfo: GetInfo = function (name, age) {
  const nameText = name.substr(0, 10);
  const ageText = age >= 35 ? 'senior' : 'junior';
  return `name: ${nameText}, age: ${ageText}`;
};
getInfo('mia', 26);
// 'name: mia, age: junior'

인터페이스로 함수를 정의할 때는 속성 이름 없이 정의한다.

// 함수 타입에 속성값 추가하기
interface GetInfo {
  (name: string, age: number): string;
  totalCall: number;
}
const getInfo: GetInfo = function (name, age) {
  getInfo.totalCall += 1;
  console.log(`totalCall: ${getInfo.totalCall}`);
};
getInfo.totalCall = 0;
getInfo('mia', 26);
// 'totalCall: 1'

인터페이스로 클래스 구현하기

리액트에서 클래스형 컴포넌트가 있지만 리액트 훅이 나오면서 상대적으로 중요도가 떨어졌으므로 거의 쓸 일이 없다고 봐도 된다.

interface Person {
  name: string;
  age: number;
  isYoungerThan(age: number): boolean;
}

class SomePerson implements Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  isYoungerThan(age: number) {
    return this.age < age;
  }
}

인터페이스 확장하기

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

interface Korean extends Person {
  isLiveInSeoul: boolean;
}

Person 인터페이스를 확장해서 Korean 인터페이스를 만든다.

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

interface Programmer {
  techStack: string;
}

interface Korean extends Person, Programmer {
  isLiveInSeoul: boolean;
}

위와 같이 여러 개의 인터페이스를 확장하는 것도 가능하다.

인터페이스 합치기

교차 타입을 사용하면 여러 인터페이스를 하나로 합칠 수 있다. 교차 타입은 교집합과 같은 기능을 한다.

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

interface Product {
  name: string;
  price: number;
}

type PP = Person & Product;
const pp: PP = {
  name: 'a',
  age: 23,
  price: 1000,
};

타입 PP는 합쳐진 두 인터페이스 Person과 Product의 모든 속성값을 포함한다. 교차 타입이 교집합과 같은 기능을 한다고 했는데 왜 name 속성 외에도 포함할까? 이는 속성의 교집합이 아니라 가질 수 있는 타입의 값에 대한 교집합이기 때문이다.

좋은 웹페이지 즐겨찾기