TypeScript 인터페이스 배열 - TypeScript inteopeiseu baeyeol

💡 해당 포스트는 TypeScript Handbook 공식 문서를 기반으로 작성되었습니다.
 📌 인터페이스?

인터페이스는 Typescript 의 여러 객체를 정의하는 일종의 규칙이며 구조입니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 키워드와 함께 정의하면 됩니다.

interface LabelValue {
  label: string;
}

function printLabel(labeledObj: LabelValue) {
  console.log(labeledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

위와 같이 인터페이스의 요소를 모두 충족하면 문제 없이 컴파일 됩니다.

 

📌 옵셔널 속성

만약

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 에 옵셔널한 속성을 부여하고 싶다면
interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
8 를 사용하면 됩니다!

다음 코드에서는

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
9 속성을 옵셔널로 지정하고 만약 주어지지 않았을 경우
interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
0 를 할당했습니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 

 

📌 읽기 전용 속성

경우에 따라서

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 의 각 속성을 처음 생성 이후 변경하지 못하도록 할 경우가 있습니다.

이때는

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
2 속성을 정의하면
interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
3 으로 만들 수 있습니다.

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.

또한

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
4 를 사용하면 배열의 각 요소를 읽을 수만 있도록 지정할 수도 있습니다.

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR

하지만 이 역시 객체 배열의 경우 객체 내부 속성까지 변경하는 것을 막지는 못합니다.

만약 객체 내부 속성을 변경하지 못하도록 하고 싶을 경우

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
2 속성을 활용합시다 😀

interface Student {
  name: string;
  readonly age: number;
}

const student1: Student = { name: "sohn", age: 26 };
const student2: Student = { name: "jeong", age: 26 };
const student3: Student = { name: "hyeon", age: 26 };

const students: ReadonlyArray<Student> = [student1, student2, student3];
students[0].name = "soooohn";
console.log(students[0]); // { name: 'soooohn', age: 26 }

students[0].age = 11; // ERROR!

 

📌 함수 타입

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 는 일반적인 객체 뿐만 아니라 함수 타입도 다음과 같이 나타낼 수 있습니다.

(사실 함수도 객체입니다.)

interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};

위 예제에서는 새롭게 정의된 함수의 인자 이름도 동일하게 했지만 실제로는 다르게 해도 상관 없습니다.

myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};

 

📌 색인 가능한 타입

이전에 함수를

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 로 기술한 것과 같이 색인 가능한 타입을 나타내는 것도 가능합니다.

예를 들어 배열에 접근할 때

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
8 과 같이 숫자형 값을 인덱스로 사용해서 해당하는 값에 접근합니다.

type Movie = string;

interface MovieArray {
  [index: number]: Movie;
}

const movies: MovieArray = ["wall-e", "iron-man", "avengers"];
console.log(movies[0]);

여기서

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
9 는
const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
0 를 가지고 있다고 표현합니다.

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
0 는 어떤 타입의 인덱스로 접근했을 때 어떤 타입의 반환 값을 얻을 수 있는지 알려 줍니다.

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
0 에서 인덱스의 타입은
const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
3 와
const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
4 만 사용할 수 있습니다.

 

또한

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
3 타입과
const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
4 타입 둘 다 명시 할 수는 있지만

반드시

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
3 타입으로 접근해서 반환되는 결과의 타입은

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
4 타입으로 접근해서 얻는 결과의 하위 타입 이어야 합니다. (할당 가능해야 합니다)

 

따라서 다음과 같은 코드는 오류가 발생합니다.

interface Card {
  name: string;
  size: number;
}

interface PhotoCard extends Card {
  imageSrc: string;
}

interface NotOkay {
  [x: number]: Card;
  [x: string]: PhotoCard;
}

이를 고치면 다음과 같이 됩니다.

interface Okay {
  [x: number]: PhotoCard;
  [x: string]: Card;   // PhotoCard 는 Card 에 할당 가능합니다!
}
💡 왜  number 타입 인덱스 결과가 `string` 타입 인덱스 결과의 하위 타입 이어야 할까?

이는 실제 Javascript 가 인덱스를 처리하는 방식을 알아야 합니다.
Javascript 는 숫자형 인덱스를 받아도 이를 문자열로 바꿔서 처리하기 때문입니다.

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
9 를 사용하면 한 가지 더 재미있는 사실이 있습니다.

이는 바로 해당

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 의 반환 타입을 강제할 수 있다는 것입니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
0

이는 객체의 속성에 접근할 때

interface Student {
  name: string;
  readonly age: number;
}

const student1: Student = { name: "sohn", age: 26 };
const student2: Student = { name: "jeong", age: 26 };
const student3: Student = { name: "hyeon", age: 26 };

const students: ReadonlyArray<Student> = [student1, student2, student3];
students[0].name = "soooohn";
console.log(students[0]); // { name: 'soooohn', age: 26 }

students[0].age = 11; // ERROR!
1 뿐만 아니라
interface Student {
  name: string;
  readonly age: number;
}

const student1: Student = { name: "sohn", age: 26 };
const student2: Student = { name: "jeong", age: 26 };
const student3: Student = { name: "hyeon", age: 26 };

const students: ReadonlyArray<Student> = [student1, student2, student3];
students[0].name = "soooohn";
console.log(students[0]); // { name: 'soooohn', age: 26 }

students[0].age = 11; // ERROR!
2 과 같이 접근할 수 있기 때문에

const elements: number[] = [1, 2, 3, 4];
const roelements: ReadonlyArray<number> = elements;

elements.push(6);
roelements.push(5);  // ERROR
roelements.length = 100;  // ERROR
9 를 지정한 경우 모든 속성의 반환 타입이 동일하게 설정됩니다.

 

또한

interface Point {
  readonly x: number;
  readonly y: number;
}

const p1: Point = { x: 10, y: 20 };
p1.x = 10;  // ERROR: Cannot assign to 'x' because it is a read-only property.
2 속성을 함께 사용하면 이전에 사용한
interface Student {
  name: string;
  readonly age: number;
}

const student1: Student = { name: "sohn", age: 26 };
const student2: Student = { name: "jeong", age: 26 };
const student3: Student = { name: "hyeon", age: 26 };

const students: ReadonlyArray<Student> = [student1, student2, student3];
students[0].name = "soooohn";
console.log(students[0]); // { name: 'soooohn', age: 26 }

students[0].age = 11; // ERROR!
5 를 구현할 수 있습니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
1

 

📌 클래스 타입

아마 대부분의 다른 객체 지향 언어에서

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 의 가장 많은 쓰임새는

클래스가 특정 메서드를 모두 구현 했는지 검사하는 용도 일 것입니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
2

대부분

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 는 클래스의
interface Student {
  name: string;
  readonly age: number;
}

const student1: Student = { name: "sohn", age: 26 };
const student2: Student = { name: "jeong", age: 26 };
const student3: Student = { name: "hyeon", age: 26 };

const students: ReadonlyArray<Student> = [student1, student2, student3];
students[0].name = "soooohn";
console.log(students[0]); // { name: 'soooohn', age: 26 }

students[0].age = 11; // ERROR!
8 메서드를 표현합니다.

이는 다른 개발자가

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 를 통해
interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
0 정보에 접근할 수 없도록 하기 위함입니다.

 

📌 인터페이스 확장하기

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 는 다른
interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 를 확장해서 사용할 수 있습니다.

이를 통해 중복된 속성을 보다 유연하게 관리할 수 있습니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
3

interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
3 의 경우 다중 상속을 지원하지 않지만

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 는 다중 상속을 지원합니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
4

 

📌 클래스를 확장한 인터페이스

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 는 클래스 상속 시
interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
0 와 같이 보호되는 맴버 속성도 함께 상속 받습니다.

이때 해당 클래스 맴버의 실제 구현부는 제외하고

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 와 같이 상속 받습니다.

interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
5

위 예제에서

interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
8 과
interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
9 의 경우 각각
myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
0 을 상속받고 있습니다.

이때

interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
8 의 경우
myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
2 이라는
interface UIButtonConfig {
  name?: string;
  width: number;
}

function createUIButton(config: UIButtonConfig): HTMLButtonElement {
  const $element = document.createElement("button");
  $element.name = config.name || "default";
  $element.style.width = `${config.width}`;
  return $element;
} 
6 를 구현하고 있는데,

myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
2 이
myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
0 이라는 클래스를 확장하면서

interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
0 맴버인
myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
7 까지 요구하고 있는 상황입니다.

 

마지막에

myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
8 에서 에러가 발생하는 이유가 바로 여기에 있습니다.

자체적으로 동일한 이름을 가지는

interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
0 맴버를 생성했지만

이는

myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
0 클래스의
interface FetchFunc {
  (url: string, method: string): object;
}

let myFetch: FetchFunc;
myFetch = function (url: string, method: string) {
  // ...
  return { name: "", age: 20 };
};
0 맴버와는 별도의 맴버입니다.

따라서

myFetch = function (a: string, b: string) {
  // ...
  return { name: "", age: 20 };
};
0 클래스를 확장해줘야 올바르게 사용할 수 있습니다.

반응형

공유하기

게시글 관리

구독하기owen.dev.log 🌳

'🎨 Frontend > TypeScript' 카테고리의 다른 글

[TypeScript] 리터럴 타입(Literal Types)  (0)2022.02.20[TypeScript] 함수(Functions)  (0)2022.02.20[TypeScript] 기본 타입 (Basic Types)  (0)2022.02.20[Effective TypeScript 정리] CH6. 타입 선언과 @types  (0)2022.02.20[Effective TypeScript 정리] CH5. any 타입 다루기  (0)2022.02.20