쉽게 시작하는 타입스크립트: 3 - 인터페이스

Bumgu·2024년 1월 22일
0

타입스크립트에서 interface란 객체 타입을 정의 할 때 사용하는 문법입니다.
Java나 C++등 객체지향언어를 사용해 봤다면 개념을 알고 있을 것입니다.

오늘은 타입스크립트의 인터페이스를 이용한 타입 정의와 타입스크립트의 인터페이스에 대해 포스팅 하겠습니다.


1. 객체 타입 정의

interface User = {
	name: string;
    age: number;
}

string타입의 namenumber타입의 age 속성이 있습니다.

이 인터페이스를 지정한 객체를 생성하려면 아래처럼 하면 되겠죠.

let bumgu: User = { name: 'bumgu', age: 18 };

여기에 인터페이스에 정의되어있지 않은 속성을 정의하거나 다른 타입을 정의 하면 에러가 발생합니다.

kelly객체는 number타입으로 정의된 속성에 string값을 넣어서 에러가 발생했고
john객체는 인터페이스에 정의 되지 않은 속성을 정의해서 에러가 발생했습니다.

이처럼 인터페이스를 이용하여 객체의 속성과 들어갈 데이터 타입을 정확하게 정의 할 수 있습니다.

2. 함수 타입 정의

2-1. 함수 파라미터 타입

function logAge (someone) {
	console.log(someone.age);
}

위와 같은 logAge함수가 있습니다. someone이라는 인자를 받아 인자안의 age속성을 출력하는 함수입니다. 여기서 인자 someone은 객체라는 것을 알 수 있습니다. 특정 데이터에 속성이 존재하려면 해당 데이터가 객체여야 하기 때문입니다.
이 함수의 파라미터를 좀 더 명시적으로 선언하려면 아래와 같이 인터페이스를 이용하여 타입을 선언 할 수 있습니다.

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

function logAge (someone: User) {
	console.log(someone.age);
}
let kelly = { name: 'kelly', age: 18 };
logAge(kelly) // 18

이름은 kelly, 나이는 18 인 kelly객체를 생성해 logAge()함수의 인자로 넘겼습니다. 인자가 함수 파라미터 타입을 만족하기 때문에 에러없이 실행이 됩니다.
만약 kelly객체를 선언할 때 누락된 속성이 있다면 에러가 발생합니다.

logAge()함수의 파라미터 타입을 User인터페이스로 지정해 놨기 때문에 age속성이 누락되었다고 친절히 알려줍니다.
이처럼 함수의 파리미터에 인터페이스를 정의하면 조건을 만족하는 데이터만 인자로 넘길 수 있습니다.

2-2. 함수 반환 타입

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

function getUser (someone: User) {
	return someone;
}

이 코드는 앞서 사용한 User인터페이스를 똑같이 정의하고 getUser()라는 함수를 추가로 정의 했습니다. getPerson() 함수는 User타입의 데이터를 받아 그대로 반환 해줍니다.
따라서 함수 이름에 커서를 올려보면 아래와 같이 함수의 반환 타입이 추론 됩니다.

이 함수의 반환 타입을 명시적으로 표시하기 위해 인터페이스로 함수의 반환타입 을 정의 할 수 있습니다.

function getUser (someone: User): User {
	return someone;
}

이렇게 반환타입도 User로 명시 할 경우, getUser() 함수의 반환값을 할당한 변수에 커서를 올려보면 해당 타입으로 추론이 됩니다.

3. 옵셔널

Day1에서도 옵셔널에 대해 설명했습니다.
인터페이스에서 옵셔널도 같습니다.

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

function logAge (someone: User) {
	console.log(someone.age);
}

위에서 사용했던 logAge() 함수입니다. 파라미터의 타입이 User타입이고, User타입은 name,age 속성을 받기 때문에 두가지의 속성값이 모두 있어야 정상적으로 실행이 됩니다.

let kelly = { age: 18 };
logAge(kelly)

이렇게 name속성을 누락할 경우

#2 에서 나왔듯, 에러가 발생합니다.
선택적으로 속성값을 받을 경우 ?를 사용해 옵셔널 속성을 부여해주겠습니다.

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

?를 추가했더니 에러가 사라지며 정상적으로 작동합니다.

이렇게 되면 logAge()함수의 인자에 User인터페이스를 만족하는 객체를 인자로 넘겨야 하지만, name속성은 있어도 되고 없어도 됩니다.

4. 인터페이스 상속

인터페이스의 상속으로 타입 정의를 확장할 수 있습니다.
상속은 객체 간 관계를 형성하며, 상위(부모)클래스의 내용을 하위(자식)클래스에게 물려주어 사용하거나 확장하는 것을 말합니다.

4-1 상속

클래스를 상속 받을때 extends란 예약어를 사용합니다. 인터페이스를 상속받을 때도 동일하게 extends예약어를 사용합니다.

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

Person인터페이스는 nameage 속성이 있습니다.

interface Developer extends Person {
  skill: string;
}

이후 Developer인터페이스는 Person 인터페이스를 상속받아 속성을 추가했습니다.
이렇게 상속받는 다면 Developer인터페이스는 마치 아래와 같이 정의 한것과 같습니다

interface Developer extends Person {
    name: string;
    age: number;
    skill: string;
}

그래서 Developer인터페이스를 지정한 객체는 PersonDeveloper의 속성들을 모두 사용 할 수 있습니다.

let webDeveloper: Developer = {
  name: 'bumgu',
  age: 18,
  skill: 'typescript'
}

4-2 상속할 때 참고 사항

상위 인터페이스의 타입을 하위 인터페이스가 상속받아 타입을 정의 할때 상의 인터페이스의 타입과 호환이 되어야 합니다. 이 호환이된다 라는 것은 상위 에서 정의된 타입을 사용해야 한다는 의미 입니다.

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

interface Developer extends Person {
  name: number;
}

위의 코드는 Developer인터페이스가 Person을 상속받지만 이미 정의된 name의 타입을 잘못 사용하고 있습니다.

이럴 경우 Developer인터페이스가 Person인터페이스를 잘못 확장하고 있고, name이 호환되지 않는다고 에러가 발생합니다.
이처럼 인터페이스를 상속할 때는 상위 인터페이스에 정의된 타입을 자식 인터페이스에서 보장해주어야 합니다.

4-3 여러번 상속하기

타입스크립트의 인터페이스는 여러번 상속이 가능합니다.

interface Hero {
  power: boolean;
}

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

interface Developer extends Person {
  skill: string;
}

여기서 DeveloperPerson을 상속받고 PersonHero를 상속받습니다.
그리고 Developer타입을 지정한 객체를 생성하겠습니다.

Developer타입을 지정한 객체를 생성하고 po까지만 쳤는데 Hero인터페이스의 power속성이 가이드 됩니다.
이유는 DeveloperPerson을 상속받고 PersonHero를 상속받았기 때문이죠.

Developer인터페이스는 PersonHero의 속성을 모두 상속받고 Developer타입을 지정한 bumgu객체는 모든 속성을 사용할 수 있습니다.

5. 인덱스 시그니처

인덱싱이란 console.log(객체['user']); 혹은 console.log(배열[0]); 과 같이 객체의 특정 속성을 접근하거나 배열의 특정 요소에 접근하는 것을 의미합니다.

인덱스 시그니처(index signature)란 정확한 속성 이름을 명시하지 않고 속성의 이름과 속성 값으 타입을 정의하는 문법입니다. 이렇게 들으면 무슨 말인지 모르겠으니 예시 코드를 보겠습니다.

interface SalaryInfo {
	junior: string;
    senior: string;
}

SalaryInfo인터페이스에는 주니어와 시니어에 대한 타입이 정의 되어있습니다.

let salary: SalaryInfo = {
	junior: '100원',
    senior: '200원'
}

SalaryInfo인터페이스에는 속성이 juniorsenior 두가지 속성밖에 없기 때문에 다른 속성을 선언할 수는 없습니다.

let salary: SalaryInfo = {
	junior: '100원',
    senior: '200원',
    ceo: '500원'
}

라고 선언할 경우,

당연히 에러가 발생합니다.
하지만 만약 salary에 고정된 junior, senior외에 다른 속성들이 들어온다면 어떡할까요? 계속해서 인터페이스를 수정해줘야하는 번거로움이 생길 것 입니다.
이때 사용하는것이 인덱스 시그니처입니다.

위의 인터페이스를 인덱스 시그니처를 사용한 인터페이스로 바꿔보겠습니다.

interface SalaryInfo {
	[positon: string]: string;
}

인덱스 시그니처를 적용한 문법입니다.
속성의 이름은 string타입이고, 속성 값 타입은 string이라는 의미이며,
속성의 이름값string타입의 어떤것이든 상관없습니다.

이와 같이 SalaryInfo를 지정받은 객체에서 타입만 지켜주면 어떠한 속성이든 추가가 가능해집니다. 이렇게 하면 여러 속성값이 들어오는 객체일경우 새로운 속성값이 들어올때 인터페이스를 수정해야하는 번거로움이 사라집니다.

객체의 속성 이름과 속성 값이 정해져 있는 경우에는 명시적으로 타입을 지정해주는 것이 효과적이지만, 속성의 이름은 모르지만 속성 이름과 값의 타입을 아는 경우에는 인덱스 시그니처가 효과적 일 것 입니다.


이상으로 인터페이스에 대한 내용을 포스팅했습니다.
감사합니다.

profile
Software VS Me

0개의 댓글