안녕하세요.
Hynn 입니다.
이번 포스팅에서는 타입스크립트에서 인터페이스의 의미와 사용에 대한 기본적인 사항을 학습해보도록 하겠습니다.
타입스크립트에서 인터페이스는 매우 중요한 개념으로 사용되며, 이 개념을 잘 사용하면, 코드의 재사용성, 구조를 향상하는데 큰 도움이 됩니다.
=============
1. 인터페이스란?
2. 인터페이스를 사용하여 얻는 이점 & 사용방법
=============
1. 인터페이스란?
인터페이스는 타입스크립트에서 가장 중요한 개념 중 하나라고 생각됩니다.
바로 타입스크립트에서 코드의 품질, 유지관리성을 향상시켜줄 뿐 아니라, 협업에서도 인터페이스 정의를 사전에 협의하고 시작을 하면, 코드가 문서화가 되고, 이를 통해 가독성이나 품질을 향상시키는 가장 중요한 수단이라고 생각합니다.
인터페이스는 객체, 그리고 재사용성을 위한 생성자(클래스)의 동작을 "문서화"하거나 기술하는 방법을 명시적으로 작성하게 됩니다. 이렇게 작성된 코드는 개발자간의 코드를 사용하거나, 메서드를 호출할때 이 코드의 의도, 사용에 대한 명확한 전달이 가능하므로, 서로간의 의사소통에서 불필요한 논의가 줄어들 뿐 아니라, 코드의 안정성을 향상시킬 수 있습니다. 당연히 의도가 잘 전달된 코드라면 올바른 사용이되고, 이를 통해 안정성이 향상이될 것입니다.
또한 이러한 안정성을 향상시키기 위해서는 구조를 명확하게 정의해야 합니다. 그리고 이전 포스팅에서 다루었던 타입체크에서도 명확한 타입으로 요청/응답하므로, 이러한 처리에서는 타입스크립트 언어의 특성인 정적 기반의 특성에 가장 잘 부합하는 기능이기도 합니다.
이를 통해 얻는 장점을 구체적으로 정리해보도록 하겠습니다.
2. 인터페이스를 사용하여 얻는 이점
인터페이스를 사용하여 얻는 이점은 크게 5가지로 구분됩니다.
이를 단계별로 하나씩 나누어보도록 하겠습니다.
1) 타입 체크
인터페이스 역시 타입체크를 기본으로 가져갑니다.
인터페이스를 활용하기에 따라서, 반환되는 결과의 타입을 지정할 수 있습니다.
가령 예를 들면 아래와 같이 설정할 수 있습니다.
interface User {
name: string;
email: string;
}
이렇게 지정하면, 타입을 설정할 때, User 라는 타입으로 지정하면, 타입스크립트는 리턴값이 User 객체에 정의된 타입과 일치하는지를 확인합니다. 즉, User 객체 안에 있는 name, email 이라는 속성이 있어야하고, 각 속성의 타입이 String 이 아니라면 오류를 발생시킵니다.
이를 통해, 기존의 원시타입 객체만을 정의하는 것이 아니라, 이러한 객체 내부의 속성까지 정의함으로, 타입체크를 보다 엄격하게 사용할 수 있습니다.
예를 들어, 회원가입을 처리할려고 할 때, TypeScript 로 이를 User 라는 객체를 지정하면, 정해진 형식으로, 의도한 형식으로만 요청응답을 처리할 수 있고, 이를 통해, 회원가입시 보다 엄격하게 검사함으로써, 형식에 맞지 않는 객체는 받을 수 없도록 타입체크 단계에서도 작성할 수 있게 됩니다.
2) 애플리케이션 구조 정의
애플리케이션 구조를 보다 구체적으로 정의할 수 있습니다. 즉, 사전에 이러한 정의를 구현함으로서, 개발과정에서 각자 혼선을 줄일 수 있고, 가독성과 유지관리성에서 많은 이점이 존재합니다.
두가지 예시를 사용해서, 확인해보도록 하겠습니다.
먼저 학생관리 시스템의 예시를 들어보겠습니다.
먼저 학생의 대한 공통 인터페이스를 정의합니다.
interface Student {
id: number;
name: string;
age: number;
}
여기에, 특정 정보를 이제 추가하는 형태로 작성이 가능합니다.
이러한 학생관리 시스템에서, 시험점수나, 전공점수등을 정의할 수 있습니다.
interface Student {
id: number;
name: string;
age: number;
major: Major;
exam : Exam1
}
interface Major {
department: string;
fieldOfStudy: string;
}
interface Exam1 {
examA: number;
examB: number;
}
이렇게 하면, Major 라는 전공정보와, Exam1 이라는 시험정보의 인터페이스가 추가되며, Student 인터페이스는 이 두 인터페이스의 의존하고 있는 구조를 띄게 됩니다.
이를 실제 출력하면, 아래와 같은 구조로 출력이 됩니다.
let student: Student = {
id: 1,
name: "Hynn",
age: 20,
major: {
department: "Computer Science",
fieldOfStudy: "Software Engineering"
}
exam : {
exam1 : 100,
exam2 : 100
}
};
이러한 구조를 정의함으로써, 학생정보에 대한 애플리케이션 구조를 정의했습니다.
즉, 이렇게 작성한 애플리케이션 구조는, 모든 학생에게 적용되며, 이런 형태로 명확하게 정의된 애플리케이션 구조는 한눈에 보기에도 편리하고, 어떠한 항목에 어떤 정보가 있는지 명확하게 파악할 수 있습니다. 즉, 기본 애플리케이션 구조가 명확하게 정리가된다면, 이러한 코드를 유지보수를 함으로써, 정보를 추가하거나 삭제, 수정이 가능해지고, 인터페이스를 수정함으로써, 전체코드에 적용도 빠르게 처리할 수 있습니다.
3) 코드의 안정성
타입체크에 연속된 장점으로써, 타입스크립트로 정의한 구조에 대한 안정성을 담보받게 됩니다.
즉, 인터페이스를 통해, 객체의 타입을 명시적으로 지정하고, 인터페이스의 구조를 따르도록 설계하면, 그에 맞는 구조의 데이터를 사용하지 않으면, 타입스크립트에서 이를 체크하여, 오류를 반환하고, 오류 역시 명확하게 타입에러를 출력합니다.
이를 통해, 코드의 안정적인 작동이 가능하도록 처리하게 됩니다.
이 역시 예시를 적용하면 아래와 같습니다.
let student: Student = {
id: 1,
name: "Hynn",
age: 20,
major: {
department: "Tistory",
fieldOfStudy: "Blog"
},
exam: {
examA: 100,
examB: 100
}
};
이와같이 구조와 동일하게 적용한 객체라면, 오류가 반환되지 않고 올바르게 출력됩니다.
하지만 만약 아래와 같이, 구조를 정의한 것과 다르게 작성할 경우 아래와 같은 오류가 출력됩니다.
let student: Student = {
id: 1,
name: "John Doe",
age: 20,
major: {
department: "Computer Science",
fieldOfStudy: "Software Engineering"
},
exam: {
examA: 90
}
};
// Type '{ examA: number; }' is not assignable to type 'Exam1'.
// Property 'examB' is missing in type '{ examA: number; }' but required in type 'Exam1'.
즉, 이렇게 객체가 생성되는 시점에 TypeScript 에서 인터페이스를 사용해 애플리케이션 구조를 정의한 것과 불일치하므로, 코드에서 컴파일 오류를 발생하도록 처리합니다. 실행하지 않고도 빠르게 오류를 확인하고, 의도와 다르게 객체가 생성되었기 때문에, 이를 처리하지 않는 것이죠.
4) 재사용성과 확장성
코드의 재사용성과 확장성에도 도움이 됩니다.
인터페이스는 타입스크립트에서 클래스와 유사한 역활을 가지고 있습니다.
기존의 JavaScript 에서 클래스라는 생성자함수는 객체를 생성하는 템플릿으로, method, property 를 포함한다면, 인터페이스는 객체의 구조를 정의합니다.
즉 클래스는 인터페이스에서 정의된 객체구조를 따르도록 강제할 수 있습니다.
이렇게 하면, 한번 정해둔 인터페이스의 객체구조를 여러 클래스에서 재사용할 수 있습니다. 예를 들면, 유저객체의 경우, 다양한 위치에서 사용될 수 있습니다. 즉, 한번 정의된 객체구조를 다양한 위치에서 사용할 수 있습니다.
또한, 필요한 부분이 추가되었을때, 위의 예제의 있는 학생객체에 시험정보가 추가되면, 시험정보에만 해당 속성을 추가로 정의하거나, 수정, 삭제할 수 있습니다. 그렇게 되면 그를 사용하는 코드들도 그에 맞추어 업데이트가 필요함을 타입스크립트에서 오류의 형태로 알려줄 수 있고, 이를 통해 코드의 확장시에도 손쉽게 대응을할 수 있게 됩니다.
일반적으로 이러한 상속형태의 코드를 구현하려면 아래와 같이 작성해야 합니다.
여기서 사용하는 2개의 방법을 정리해보겠습니다.
a) extends
interface Name {
name: string;
}
interface Employee extends Named {
salary: number;
}
let employee: Employee = {
name: "Hynn",
salary: 1
};
b) Implements
interface Name {
name: string;
}
interface Employee extends Name {
salary: number;
}
class EmployeeClass implements Employee {
name: string;
salary: number;
constructor(name: string, salary: number) {
this.name = name;
this.salary = salary;
}
}
let employee = new EmployeeClass("Hynn", 1);
위의 각 예시를 보면 아래와 같이 사용의 규칙이 존재합니다.
인터페이스를 상속받는 인터페이스를 만들기 위해서는 클래스와 마찬가지로 "Extends" 를 사용합니다.
인터페이스간의 상속은 기존의 JavaScript 에서 사용하던 Class 에서 사용하는 extends 와 동일하게 사용할 수 있습니다.
즉, 인터페이스를 상속받게 되므로, 상속받는 인터페이스의 모든 속성을 사용할 수 있습니다.
반면에, 인터페이스를 클래스에 적용, 준수하도록 처리를 하려면 "Implements" 를 사용해야 합니다.
즉 일반적으로 인터페이스를 구현한 뒤, 클래스 함수를 작성하는 단계를 거치게 되며, 여기서 클래스함수는 extends 가 아니라 Implements [인터페이스명] 을 사용하도록 작성하면 되겠습니다.
이렇게 작성하면, 클래스는 인터페이스의 구조를 준수하도록 구현이 가능합니다.
단 여기서, 선택적으로 작성해야 하는 속성이 있다면, 인터페이스 구현시에도 "?" 를 붙여 처리를 해야 합니다.
위의 예시에서, 붙여보면 아래와 같이 작성할 수 있습니다.
interface Name {
name: string;
}
interface Employee extends Name {
salary?: number;
}
class EmployeeClass implements Employee {
name: string;
salary?: number;
constructor(name: string, salary: number) {
this.name = name;
this.salary = salary;
}
}
let employee = new EmployeeClass("Hynn");
위와 같이 "?"를 속성에 표시하여 작성하면, 이 속성은 필수가 아니라 "선택", 즉 Optional 한 속성이 되므로, 없을 경우 undefined 로 반환되며, 값이 제공되지 않아도, 준수한것으로 처리됩니다. 이러한 옵션을 적절하게 활용하면, 적절한 위치에서 사용할 수 있게 됩니다.
클래스와 인터페이스를 사용함으로써 얻는 이점은 정말 좋은 이점이라고 할 수 있습니다. 다음 포스팅에서는 이러한 이점을 활용해 TypeScript 를 본격적으로 사용해보도록 할 예정입니다.
감사합니다.
'개발공부일지 > TypeScript' 카테고리의 다른 글
TypeScript - 컴파일러 & 트랜스파일러 적용하기 (1) | 2023.04.19 |
---|---|
TypeScript - 타입스크립트 기초 알아보기 (0) | 2023.04.18 |
댓글