typescript

[typescript] 인터페이스(interface)

sewonzzang123 2023. 8. 14. 19:30
반응형


타입스크립트에서 인터페이스(interface)는 객체 타입을 정의할 때 사용하는 문법입니다.

 

인터페이스로 타입을 정의할 수 있는 부분은 다음과 같습니다.

  • 객체의 속성과 속성 타입
  • 함수의 파라미터와 반환 타입
  • 함수의 스펙(파라미터 개수와 반환값 여부 등)
  • 배열과 객체를 접근하는 방식
  • 클래스

1. 인터페이스를 이용한 객체 타입 정의

 

 

Member이라는 인터페이스를 선언한 코드입니다.

인터페이스의 속성으로 name과 age를 각각 문자열과 숫자 타입으로 정의했습니다.

정의한 객체에 인터페이스를 지정하면 아래와 같습니다.

interface Member{
	name: String,
    age: number
}

var wonwoo: Member = {name:'원우', age:28}

 

wonwoo라는 객체에 age를 '36'으로 할당하거나, hobby:'게임' 같은 속성을 정의한다면, Member인터페이스에 정의되어 있지 않은 타입과 객체의 속성을 사용하여 에러가 발생하게 됩니다.

 

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

 


2. 인터페이스를 이용한 함수 타입 정의

 

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

 

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

function logAge(someone: Person){
	conosle.log(someone.age);
}

var wonwoo = {name:'Jeon', age:28};
logAge(wonwoo);

logAge를 동작할때, 누락된 속성이 있을 때는 에러가 발생합니다.

(ex) var wonwoo = {name:'Jeon'}:)

위에 예시된대로 name속성만 있게 되는 경우에는 logAge()의 파라미터 타입과 일치하지 않아 에러가 발생합니다.

 

이처럼 함수의 파라미터에 정의한 타입 조건을 만족하는 데이터만 인자로 넘길 수 있습니다.

 

2-2. 함수 반환 타입 정의

 

getPerson()이라는 함수는 Person 인터페이스 타입의 데이터를 받아 그대로 반환해 주고 있습니다.

따라서 함수 이름에 마우스 커서를 올려 보면 함수의 반환 타입이 추론되는 것을 확인할 수 있습니다.

 

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

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

function getPerson(someone: Person): Person {
	return someone;
}

var dk = getPerson({name:'seokmin', age:27});

3. 인터페이스의 옵션 속성

 

인터페이스로 정의된 객체의 속성을 선택적으로 사용하고 싶을 때 옵션 속성을 사용합니다.

 

옵션 속성을 사용하지 않았을 경우, logAge()의 인자가 name과 age 속성을 가진 객체여야 하는데 age속성만 정의된 객체를 받아서 에러가 생깁니다.

자바스크립트로 코드를 작성할 때는 객체의 일부 속성만 작성한 후 추후에 객체의 속성을 추가해도 되었는데 타입스크립트로 작성을 하게 되니 제약 사항이 생기기 시작합니다.

 

이때 사용할 수 있는 것이 옵션 속성(optional property)입니다.

Person 인터페이스의 name속성에 ?를 붙이면 에러가 사라집니다.

 

이렇게 상황에 따라서 유연하게 인터페이스 속성의 사용 여부를 결정할 수 있는 것이 옵션 속성입니다.

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

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

var saay = {age: 111};
logAge(saay);

4. 인터페이스 상속

 

상속은 객체 간 관계를 형성하는 방법이며, 상위(부모) 클래스 내용을 하위(자식) 클래스가 물려받아 사용하거나 확장하는 기법을 의미합니다. 자바스크립트에서도 클래스로 상속을 구현할 수 있습니다.

 

class Person{
	constructor(name, age){
    	this.name = name;
        this.age = age;
        }
        
    logAge(){
    	console.log(this.age);
    	}
}

class Developer extends Person{
	constructor(name, age, skill){
    	super(name, age);
        this.skill = skill;
        }
        
    logDeveloperInfo(){
    	this.logAge();
        console.log(this.name);
        console.log(this.skill);
        }
}

이 코드는 Person 클래스를 정의하고 Developer 클래스에서 상속받아 클래스 내부를 구현한 것입니다.

Person 클래스에 name과 age 속성이 정의되어 있고 logAge() 메서드가 구현되어 있기 때문에 Developer에서 extends로 상속받으면 이 속성과 메서드를 모두 사용할 수 있습니다.

 

4-1. 인터페이스의 상속이란?

 

클래스를 상속받을 때 extends란 예약어를 사용했습니다.

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

 

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

interface Developer extends Person{
	skill: string;
}

var ironman: Developer = {
	name: '아이언맨',
    age: 22,
	skill: '만들기'
}

이 코드는 Person 인터페이스를 선언하고 Developer 인터페이스에 extends로 상속한 것입니다.

Developer 인터페이스는 다음과 같이 정의한 효과가 나타납니다.

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

 

4-2. 인터페이스를 상속할 때 참고 사항

 

인터페이스를 상속할 때 주의해야 할 점이 있습니다. 상위 인터페이스의 타입을 하위 인터페이스에서 상속받아 타입을 정의할 때 상위 인터페이스의 타입과 호환이 되어야 합니다.

호환이 된다는 것은 상위 클래스에서 정의된 타입을 사용해야 한다는 의미입니다.

 

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

interface Developer extends Person{
	name: number;
}

Developer 인터페이스에서 Person 인터페이스를 상속받았는데 Person 인터페이스에서 선언된 name 속성 타입을 자식 인터페이스인 Developer 인터페이스에서 다른 타입으로 정의하자 에러가 발생했습니다.

에러 메시지를 보면 'Developer 인터페이스에서 정의한 name 속성의 number 타입이 Person 인터페이스에서 정의한 name 속성이 string 타입과 호환되지 않는다'고 나옵니다.

 

이처럼 인터페이스를 상속하여 사용할 때는 부모 인터페이스에 정의된 타입을 자식 인터페이스에서 모두 보장해 주어야 합니다.

 

상속때 또 알아 두어야 할 점은 상속을 여러 번 할 수 있다는 것입니다.

interface Hero{
	power: boolean;
}

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

interface Developer extends Person{
	skill: string;
}

var ironman: Developer = {
	name: '아이언맨',
    age: 21,
    skill: '만들기',
    power: true
}

Hero 인터페이스를 Person 인터페이스가 상속받고, Person 인터페이스를 Developer 인터페이스가 상속받았습니다.

그래서 ironman 변수에 Developer 인터페이스를 정의하면 인터페이스 3개에 제공하는 속성을 모두 타입에 맞게 선언해 주어야 합니다.

이처럼 상속을 여러 번 받아서 정의할 수도 있습니다.

 


5. 인터페이스를 이용한 인덱싱 타입 정의

 

인터페이스로 객체와 배열의 인덱싱 타입을 정의하는 방법을 알아보겠습니다.

인덱싱이란 다음과 같이 객체의 특정 속성을 접근하거나 배열의 인덱스로 특정 요소에 접근하는 동작을 의미합니다.

 

var user = {
	name: 'saay',
    admin: true
};
console.log(user['name']); // saay

var companies = ['samsung','google','naver'];
console.log(companies[0]); // samsung

user 객체의 name 속성에 접근하기 위해 user['name'] 코드를 사용했습니다.

문자열 배열로 선언된 companies 변수도 첫 번째 요소에 접근하려고 인덱스 0을 사용했습니다.

 

위의 코드처럼 user['name'] 형태로 객체의 특정 속성에 접근하거나 companies[0] 형태로 배열의 특정 요소에 접근하는 것을 인덱싱이라고 합니다.

 

5-1. 배열 인덱싱 타입 정의

 

배열을 인덱싱 할 때 인터페이스로 인덱스 요소와 타입을 정의할 수 있습니다.

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

var companies: StringArray = ["sam", "nav", "google"];

companies[0]; // sam
companies[1]; // nav

StringArray 인터페이스의 속성에 [index: number]라는 코드가 있습니다. 이 코드는 어떤 숫자든 모두 속성의 이름이 될 수 있다는 의미입니다. 그리고 [index: number]: string; 에서 속성 이름은 숫자고 그 속성 값으로 문자열 타입이 와야 한다는 의미입니다.

 

배열의 첫번째 요소를 숫자인 인덱스 0을 이용하여 접근했을 때 'sam'이라는 문자열이 나온다는 것을 알 수 있습니다.

이것을 다시 StringArray 인터페이스에 대입해 보면 배열의 인덱스 타입이 [index: number]가 되고, 인덱스로 접근한 요소의 타입이 string이 됩니다.

 

여기서 StringArray 인터페이스의 인덱스 타입을 다음과 같이 문자열로 변경하면 타입에러가 발생합니다.[index: string]: string;

 

원래 배열의 인덱스는 숫자여야 하는데 문자열로 인덱스 타입을 강제하여 배열 정의에 위배되었다는 것을 알 수 있습니다.

companies['첫번째 인덱스']; // undefined
companies[0];		 // sam

배열은 숫자 인덱스를 이용해서 특정 요소에 접근할 수 있습니다.  인덱스 타입이 number가 아닌 string 타입으로 선언된 StringArray 인터페이스는 더 이상 배열의 타입으로 사용할 수 없습니다.

 

5-2. 객체 인덱싱 타입 정의

 

객체 인덱싱의 타입도 인터페이스로 정의 할 수 있습니다.

interface SalaryMap {
  [level: string]: number;
}

var salary: SalaryMap = {
  junior: 100,
};

var money = salary['junior'];

SalaryMap 인터페이스는 속성 이름이 문자열 타입이고 속성 값이 숫자 타입인 모든 속성 이름/속성 값 쌍을 허용하겠다는 의미입니다.

 

money객체를 보면, 객체의 속성에 접근할 때 객체['속성이름'] 형태로 접근했습니다.

SalaryMap 인터페이스에서 속성 이름이 문자열이면 속성 값은 숫자라고 타입을 정의했기 때문에 salary['junior'] 결과는 number 타입입니다.

 

객체의 속성에 접근하는 방법은 salary['junior'] 또는 salary.junior 모두 가능합니다.

다만 속성 이름에 숫자나 -등 특수 기호가 들어가면 .junior 방식으로 접근할 수 없기 때문에 ['junior'] 방식으로 접근해야 합니다.

 

5-3. 인덱스 시그니처란?

 

interface SalaryMap {
  [level: string]: string;
}

정확히 속성 이름을 명시하지 않고 속성 이름의 타입과 속성 값의 타입을 정의하는 문법을 인덱스 시그니처(index signature)라고 합니다.

인덱스 시그니처는 단순히 객체와 배열을 인덱싱 할 때 활용될 뿐만 아니라 객체의 속성 타입을 유연하게 정의할 때도 사용됩니다.

 

위와 같이 인덱스 시그니처를 정의하면 SalaryMap 인터페이스로 정의한 객체에 무수히 많은 속성을 추가할 수 있습니다.

 

let salary: SalaryMap = {
	junior: '100d',
    mid: '400d',
    senior: '700d',
    ceo: '0d',
    newbie: '50d',
};

속성 이름이 문자열이고 속성 값의 타입이 문자열이기만 하면 1개든 100개든 n개든 모두 추가할 수 있는 장점이 생깁니다.

 

5-4. 인덱스 시그니처는 언제 쓸까?

일일이 인터페이스의 타입을 구체적으로 정의하기보다 인덱스 시그니처가 더 좋은 것 같은데, 그럼 인덱스 시그니처만 쓰면 될까요?

인덱스 시그니처를 언제 쓰면 좋은 것인지 알아봅시다.

 

다음과 같이 객체의 속성 이름과 개수가 구체적으로 정의되어 있다면 인터페이스에서 속성 이름과 속성 값의 타입을 명시하는 것이 더 효과적입니다.

interface User{
	id: string;
    name: string;
}

let wonwoo: User = {
	id: '1',
    name: '원우'
};

 

다음과 같이 인덱스 시그니처가 적용되어 있는 경우에는 코드를 작성할 때 구체적으로 어떤 속성이 제공될지 알 수 없어 코드 자동완성이 되지 않기 때문입니다.

interface User{
	[property: string]: string
}

let wonwoo: User = {

};

 

여기에서 User라는 인터페이스에는 id와 name 속성이 무조건 들어간다고 한다면 다음과 같이 섞어서 정의할 수도 있습니다.

interface User{
	[property: string]: string
	id: string;
    name: string;
}

let wonwoo: User = {
	id: '1',
    name: '원우'
};

 

이처럼 객체의 속성 이름과 속성 값이 정해져 있는 경우에는 속성 이름과 속성 값 타입을 명시해서 정의하였고, 속성 이름은 모르지만 속성 이름의 타입과 같이 값의 타입을 아는 경우에는 인덱스 시그니처를 활용합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형