2. 인터페이스
2-1. 인터페이스란(Interface) ?
정의한 약속 혹은 규칙을 의미하는데, 이는 인터페이스에 선언된 프로퍼티 또는 메소드의 구현을 강제하여 일관성을 유지할 수 있도록 하는 것.
- 인터페이스(Interface)가 정의할 수 있는 약속 혹은 규칙
- 객체의 스펙(속성과 속성의 타입)
- 함수의 파라미터
- 함수의 스펙(파라미터, 반환 타입 등)
- 배열과 객체를 접근하는 방식
- 클래스
비교 예시 :
- 인터페이스를 사용하지 않은 경우
function printLabel(labeledObj: { label: string }) { console.log(labeledObj.label) } let myObj = { size: 10, label: "Size 10 Object" } printLabel(myObj)
- 인터페이스를 사용한 경우
* 인터페이스 안의 요구들은 순서를 지킬필요는 없고 조건을 충족하기만 하면 된다. interface LabeledValue { label: string } function printLabel(labeledObj: LabeledValue) { console.log(labeledObj.label) } let myObj = { size: 10, label: "Size 10 Object" } printLabel(myObj)
2-2 선택적 프로퍼티 (Optional Properties) :
선택적 프로퍼티들은 객체 안의 몇개의 프로퍼티만 채워서 전달하는 패턴을 만들때 유용하다.
선택적 프로퍼티는 선언에서 프로퍼티 이름 끝에 ? 를 붙여 표시한다.
예시 코드
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100}; // 만약에 config 자료가 없을시 출력해줄 기본값
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
2-3 읽기전용 프로퍼티 (Readonly properties) :
객체가 처음 할당될 때 이후 재할당이 불가능하다. 프로퍼티 이름 앞에 readonly를 넣어서 이를 지정할 수 있다
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 20 // 에러 : Cannot assign to 'x' because it is a read-only property.
p1 = { x: 20, y: 20 }; // 에러가 안난다 > 원래는 에러 나는게 정상이고 에러가안나면 감지를 못하는것?
console.log(p1) // { x: 20, y: 20 }
Array와 동일한 ReadonlyArray 타입을 제공하여 ReadonlyArray를 일반 배열에 재할당이 불가능하도록 한다.
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // 오류!
ro.push(5); // 오류!
ro.length = 100; // 오류!
a = ro; // 오류!
// ro= [3,4,5,6] // 에러가 안난다 > 원래는 에러 나는게 정상이고 에러가안나면 감지를 못하는것
단 타입 단언(type assertion)으로 오버라이드하는 것은 가능하다
a = ro as number[];
2-4 const와 readonly :
변수는 const를 프로퍼티는 readonly를 사용한다.
2-5 초과 프로퍼티 검사 (Excess Property Checks):
객체 리터럴이 "대상 타입 (target type) = SquareConfig "이 갖고 있지 않은 프로퍼티를 갖고 있으면, 에러가 발생
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black", opacity: 0.5});
//ERROR !
// Argument of type '{ color: string; opacity: number; }' is not assignable to parameter of type 'SquareConfig'.
//Object literal may only specify known properties, and 'opacity' does not exist in type 'SquareConfig'.
--> SquareConfig에 opacity란 프로퍼티가 없기 때문에 에러 발생
- 피하는 방법
- 타입단언하기
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
- 문자열 인덱스 서명 추가 (string index signatuer)
interface SquareConfig { color?: string; width?: number; [propName: string]: any; } // 타입단언보다 더 나은 방법이다.
- 객체를 다른 변수에 할당
let squareOptions = { color: "red", opacity: 0.5 }; let mySquare = createSquare(squareOptions); console.log(mySquare) //{ color: 'red', area: 100 }
- 초과 프로퍼티 에러의 대부분은 실제 버그로 피하지 않는게 좋다. 간단한 코드 경우 createSquare에 opacity도 모두 전달해도 괜찮다면, squareConfig가 이를 반영하도록 수정
2-5 함수 타입 (Function Types):
인터페이스로 함수 타입을 기술가능하며 함수 선언방식과 유사
//함수타입 생성
interface SearchFunc {
(source: string, auth: number): boolean;
// (source: string, subString: string) 라는 매개변수를 받으면 boolean값 반환
}
//함수 타입임을 변수에 선언
let mySearch: SearchFunc;
//함수 사용
mySearch = function(source: string, auth: number) {
console.log(source);
console.log(auth);
return auth >= 3; // auth가 3이상일경우 true
}
console.log(mySearch("TEST subString",2)); //false
2-6 인덱서블 타입 (Indexable Types) :
array[0]와 같은 index를 활용한 타입 설정도 가능하다.
하지만 인덱스 서명은 문자열과 숫자타입 두가지만 지원한다.
코드예제 1
// 타입을 기술하는 인덱스 시그니처 (index signature)
interface NameArray {
[index: number]: string;
}
let myArray: NameArray= ["han", "nah"];
let myStr: string = myArray[0];
console.log(myStr)
두가지 타입 모두 지원하지만 반드시 선언한 타입과 같은 타입이어야만 한다.
interface NumberDictionary {
[key: string]: number;
length: number; // 성공, length의 타입은 number로 선언
id: string; // 오류, `id`의 타입은 key와같은 number가 아님
}
코드 예제 2
interface NumberDictionary {
[key: string]: number;
length: number;
id: number;
}
let myArray: NumberDictionary= {id:2, length:3};
let myStr: number = myArray["id"];
console.log(myStr) // 2
코드예제 3
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // 성공, length는 숫자입니다
name: string; // 성공, name은 문자열입니다
}
만약 인덱스 할당을 막기 위해서는 readonly를 활용
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // 오류!
2-7 클래스 타입 (Class Types)
2-7-1 인터페이스 구현하기 (Implementing an interface) :
implements가 의미하는것은 인터페이스(interface)를 구현할 때 사용된다.
인터페이스는 클래스나 객체가 특정한 속성과 메서드를 갖도록 정의하는 역할을 한다.
예제코드
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
- 클래스에 구현된 메서드를 인터페이스 안에서도 기술할 수 있다
- 클래스의 public과 private 모두보다는, public을 기술 ( private에서는 특정 타입이 있는지 검사 못함 )
2-7-2 클래스의 스태틱과 인스턴스의 차이점 (Difference between the static and instance sides of classes):
클래스는 두가지 타입을 가진다.
- 정적(static) 타입 : 클래스 자체에 적용되는 타입으로, 클래스의 정적 속성과 정적 메서드에 대한 타입
- 인스턴스(instance) 타입 : 클래스로 생성된 객체 또는 인스턴스의 타입으로 클래스의 인스턴스를 생성해야만 해당 타입의 속성과 메서드에 접근가능
- 예제코드
class MyClass { static staticProperty: number = 123; instanceProperty: string = 'Hello'; static staticMethod() { console.log('Static method'); } instanceMethod() { console.log('Instance method'); } } // 정적 타입 사용 console.log(MyClass.staticProperty); // 123 MyClass.staticMethod(); // "Static method" // 인스턴스 생성 및 인스턴스 타입 사용 const myObject = new MyClass(); console.log(myObject.instanceProperty); // "Hello" myObject.instanceMethod(); // "Instance method"
* 생성자 시그니처(construct signature)를 포함하는 인터페이스를 정의하고, 해당 인터페이스를 클래스가 구현하려고 할 때 클래스 내부에 생성자를 정의하지않으면 에러가 난다 (클래스의 인스턴스만 검사하고 스태틱은 검사하지 않기 때문)
- 에러코드
interface MyInterface { x: number, y: number; } class MyClass implements MyInterface { constructor() { // 에러: 'MyClass'는 'MyInterface'를 구현할 수 없습니다. } }
- 수정코드
interface MyInterface { x: number; y: number; } class MyClass implements MyInterface { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } const myObj = new MyClass(10, 20); console.log(myObj.x); // 10 console.log(myObj.y); // 20
- 활용코드
interface ClockConstructor {
new (hour: number, minute: number);
}
interface ClockInterface {
tick();
}
const Clock: ClockConstructor = class Clock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}
2-8 인터페이스 확장하기 (Extending Interfaces) :
클래스처럼, 인터페이스들도 확장(extend)이 가능
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
2-9 하이브리드 타입 (Hybrid Types):
- JavaScript의 동적이고 유연한 특성 때문에, 위에서 설명했던 몇몇 타입의 조합으로 동작하는 객체가 가능
- 예제로 추가적인 프로퍼티와 함께, 함수와 객체 역할 모두 수행하는 객체
interface Counter { (start: number): string; interval: number; reset(): void; } function getCounter(): Counter { let counter = (function (start: number) { }) as Counter; counter.interval = 123; counter.reset = function () { }; return counter; } let c = getCounter(); c(10); c.reset(); c.interval = 5.0;
2-10 클래스를 확장한 인터페이스 (Interfaces Extending Classes):
인터페이스 타입이 클래스 타입을 확장하면, 클래스의 멤버는 상속받지만 구현은 상속받지 않는다.
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
- SelectableControl은 select 메서드를 가진 Control과 같은 역할
- Button과 TextBox 클래스들은 SelectableControl의 하위타입이지만 Control을 상속받고, select 메서드를 가짐
'FrontEnd_Study > TYPESCRIPT' 카테고리의 다른 글
[Typescript ] 03.함수 (0) | 2023.06.18 |
---|---|
[TypeScript] 01. 기본타입 (0) | 2023.06.18 |