이재승님의 실전 리액트 프로그래밍을 읽고 정리한 내용입니다.
자바에서 인터페이스는 클래스를 구현하기 전에 필요한 메서드를 정의하는 용도로 쓰인다. 타입스크립트에서는 좀 더 다양한 것들을 정의하는데 사용할 수 있다.
인터페이스로 타입을 정의할 때는 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 타입을 포함하는 더 큰 타입이기 때문에 타입 에러가 발생하지 않는다.
인터페이스에서 이름을 구체적으로 정의하지 않고 값의 타입만 정의하는 것을 인덱스 타입이라고 한다.
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
속성 외에도 포함할까? 이는 속성의 교집합이 아니라 가질 수 있는 타입의 값에 대한 교집합이기 때문이다.