TypeScript(타입스크립트) 인터페이스

NSH·2022년 5월 25일
0

1. 인터페이스(interface) 란?

인터페이스는 상호 간에 정의한 약속 혹은 규칙이다. 타입스크립트에서 인터페이스는 보통 아래와 같은 경우에 규칙을 정의할 수 있다.

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

2. 인터페이스 동작 방식

아래 예제를 통해서 타입스크립트의 인터페이스가 어떻게 동작하는지 알아보자.

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

const myObj = { size: 10, label: 'size 10 object' };
printLabel(myObj);
  • 타입 검사는 printLabel 호출을 확인한다.
  • printLabel 함수는 string 타입 label 을 갖는 개체를 하나의 매개변수로 가진다.
  • 컴파일러는 객체가 label 프로퍼티 이외에 size 프로퍼티도 가지고 있지만 컴파일러는 최소한 필요한 프로퍼티가 있는지와 타입이 잘 맞는지만 검사한다.

같은 예제를 인터페이스를 사용해서 다시 작성해보자.

interface LabeledValue {
	label: string;
}

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

const myObj = { size: 10, label: 'size 10 object' };
printLabel(myObj);
  • LabeledValue 인터페이스는 이전 예제의 요구사항({ label: string })과 동일하지만 이름으로 사용할 수 있다.
  • printLabel 함수 호출 시 전달한 myObj 객체가 인터페이스와 동일하게 구현할 필요는 없다.
  • printLabel 함수 호출 시 전달한 myObj 객체가 인터페이스에 나열된 요구 조건을 충족하면 허용된다.
  • 타입 검사는 프로퍼티들의 순서는 검사하지 않는다. 단지 요구하는 프로퍼티가 존재하는지와 프로퍼티들이 요구하는 타입과 일치하는지만 확인한다.

3. 선택적 프로퍼티(Optional Properties)

인터페이스의 모든 프로퍼티가 필요하지 않은 경우가 존재한다. 선택적 프로퍼티는 객체 내부의 몇 개의 프로퍼티만 채워 함수에 전달하는 option bags 같은 패턴을 만들 때 유용한다.

interface SquareConfig {
	color?: string;
  	width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
	const newSquare = { color: 'white', area: 100 };
  	
  	if(config.color) {
    	newSquare.color = config.color;
      	// Error: Property 'clor' does not exist on type 'SquareConfig'
      	newSquare.color = config.clor;
    }
  
  	if(config.width) {
    	newSquare.area = config.width * config.width;
    }
  
  	return newSquare;
}

let mySquare = createSquare({ color: 'black' });

선택적 프로퍼티는 선언할 때 프로퍼티 이름 끝에 ? 를 붙여서 표시한다.

선택적 프로퍼티의 이점은 인터페이스에 속하지 않는 프로퍼티의 사용을 방지하고, 사용 가능한 속성을 기술한다. 예를 들어 createSquare 내부의 color 프로퍼티 이름을 잘 못 입력하면 오류 메시지로 알려준다.

우아한 타입스크립트 에서는 아래와 같은 경우에 선택적 타입 대신 유니온 타입을 사용해서 명시적으로 사용을 제한시킨다.

interface Result {
	data?: string;
  	error?: Error;
  	loading: boolean;
}

declare function getUserDatas(): Result;

const result = getUserDatas();

result.data;	// string | undefined
result.error;	// Error | undefined
result.loading;	// boolean

if(result.data) {
  result.error; // Error | undefined
  result.loading // boolean
}

보통의 경우에 정상적으로 외부 데이터를 가져와서 data 가 존재하면 error 는 존재할 수 없다. 하지만 위 예제는 에러에 접근이 가능하다는 문제가 발생한다.

type Result = 
	| { loading: true }
	| { data: string; loading: false }
	| { error: Error; loading: false; }

declare function getUserDatas(): Result;

const result = getUserDatas();

if('data' in result) {
  	result.data;
  	result.loading;
  	// error, Property error dose not exist on Type { data: string; loading: false }
	result.error; 
}

유니온 타입을 사용하면 요청 중, 요청 성공, 요청 실패 상태에 따라 접근할 수 있는 데이터를 명확하게 구분해서 사용할 수 있다.

4. 읽기전용 프로퍼티 (Readonly properties)

개발을 하다보면 객체의 일부 프로퍼티의 값을 수정 불가능하게 설정해야하는 경우가 존재한다. 프로퍼티 앞에 readonly 를 넣어서 이를 지정할 수 있다.

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

const point: Point = { x: 10, y: 20 };
point.x = 20; // readonly로 수정을 시도하면 에러가 발생한다.

타입스크립트는 배열 생성 후 내부 값들을 수정할 수 없도록 할 수 있다.

const num: number[] = [1, 2, 3, 4];
const readonlyNum: ReadonlyArray<number> = [1, 2, 3, 4];

num[0] = 10;		// 성공
num.push(10);		// 성공
num.length = 100;	// 성공

readonlyNum[0] = 10;		// 오류
readonlyNum.push(10);		// 오류
readonlyNum.length = 100;	// 오류
num = readonlyNum; 			// 오류

예제 마지막 줄에서 ReadonlyArray 타입의 배열은 일반 배열에 재할당 할 수 없다. 타입 단언으로 오버라이드는 가능하다.

num = readonlyNum as number[];

5. 함수 타입 (Function Types)

인터페이스 함수는 아래와 같이 정의할 수 있다.

interface SearchFunc {
	(source: string, subString: string): boolean; 
}

함수 타입 인터페이스는 다른 인터페이스처럼 사용할 수 있다. 함수 타입의 변수를 만들고 같은 타입의 함수를 값으로 할당할 수 있다.

let mySearch: SearchFunc;

mySearch = function(sourceL string, subString: string): string {
	let result = source.search(subString);
  	return result > -1;
}

함수의 타입 검사를 위해서 매개변수의 이름이 같은 필요는 없다. 함수의 매개변수는 같은 위치에 대응되는 매개변수끼리 한번에 하나씩 검사한다.

let mySearch: SearchFunc;

mySearch = function(src: string, sub: string): boolean {
    let result = src.search(sub);
    return result > -1;
}

mySearch 변수에 SearchFunc 타입을 할당했기 때문때 타입을 직접 입력하지 않아도 인수와 반환 타입을 추론할 수 있다.

let mySearch: SearchFunc;

mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

만약 함수가 숫자나 문자열과 같은 다른 타입을 반환한다면 인터페이스에 정의된 타입과 일치하지 않다고 에러를 발생시킨다.

let mySearch: SearchFunc;

// error: Type '(src: string, sub: string) => string' is not assignable to type 'SearchFunc'.
// Type 'string' is not assignable to type 'boolean'.
mySearch = function(src, sub) {
  let result = src.search(sub);
  return "string";
};

6. 인덱서블 타입 (Indexable Types)

지금까지 사용한 인터페이스는 직접 속성의 타입을 지정했다. 그러나 객체, 배열과 같은 경우 속성이 많이 들어거나 어떤 속성이 들어올 지 확정지을 수 없는 경우 하나하나 타입을 지정할 수 없다.
이 경우에 인덱서블 타입을 활용하는 것이 좋다.

인덱서의 타입은 stringnumber 만 지정할 수 있다.

예를 들어 DB에 저장된 color 데이터를 조회하는 API를 호출한다고 가정해보자. 누군가 DB에 color 데이터를 추가할 때마다 추가된 color의 타입을 지정할 수는 없을 것이다.

// 이렇게 color 마다 타입을 지정할 수는 없을 것이다.
interface colorList {
	'white': string; 
  	'red': string;
  	.....
}

// 인덱서블 타입을 통해서 color 타입을 지정하면 모든 color를 커버할 수 있다.
interface colorList {
	[key: string]: string;
}

const colorList: colorList = getColorList();

profile
잘 하고 싶다.

0개의 댓글