안녕하세요!
TypeScript의 개념과 기본 타입 다음으로 객체 타입과 Union, Type alias(타입 별칭), interface에 대해 정리해보고자 합니다.
리터럴 타입과 type과 interface를 언제 사용하면 좋을지도 함께 작성해보고자 합니다.
📎 객체 타입
객체는 key와 value를 가지는 데이터구조로 타입을 정의할 때에는 {key: Type} 을 작성해줍니다. 해당 key가 가리키는 값에는 정의된 타입의 데이터를 담을 수 있습니다. 옵셔널 프로퍼티일 경우 프로퍼티 이름 뒤에 ?를 붙이면 됩니다.
function setPosition(pt: {x:number; y:number}) {}
function setName(obj: {first:string; last?:string;}) {}
📎 유니온 타입(Union)
타입 스크립트에서는 연산자를 사용하여 새로운 타입을 만들 수 있습니다. 유니온 타입은 서로 다른 두개 이상의 타입을 조합하여 사용하는 것으로, 정의된 타입 중 하나를 타입으로 가질 수 있습니다. 조합에 사용된 각 타입을 유니언 타입의 멤버라고 부릅니다
function printId(id: number | string) {}
유니온 타입을 사용할 때에는 하나의 타입으로 명시되지 않기 때문에 내부 코드에서는 분기를 타야하는 경우가 있을 수 있습니다. 예로 string메서드를 사용하기 위해선 입력된 매개변수의 값이 string 타입인지 체크한 후 사용할 수 있습니다. typeof 키워드를 이용해서 확인할 수 있습니다.
function printId(id: number | string) {
console.log(id.toUpperCase()); // Property 'toUpperCase' does not exist on type 'string | number'.
if (typeof id === "string") {
// 이 분기에서 id는 'string' 타입을 가집니다
console.log(id.toUpperCase());
} else {
// 여기에서 id는 'number' 타입을 가집니다
console.log(id);
}
}
유니온 타입의 장점으로 하나로 편집기에서 드롭다운으로 자동 선택이 가능하게 됩니다. 함수를 호출하는 곳에서 어떤 입력값을 넣을 수 있는지 자동완성 기능을 이용할 수 있기 때문에 효율적인 개발이 가능합니다. 또안 유니온 멤버가 아닌 값을 입력하게 되면(예를 들어 'top') 편집기에서는 해당 인수는 할당할 수 없다는 메세지를 안내받게 됩니다.
📎 Enum
typeScript에서는 enum 타입도 사용할 수 있습니다.
0~ 숫자값으로 할당되며, 기본 시작값을 줄 수 있습니다. 또한 값 자체를 할당할 수도 있습니다.
enum Direction {
Up = 1,
Down,
Left,
Right,
}
Direction.Up // 1
Direction.Down // 2
Direction.Left // 3
Direction.Right // 4
먼저 숫자형을 살펴보자면 위와 같이 사용할 수 있습니다. 위 예시는 1로 기본 시작값을 줌으로써 1,2,3,4의 값을 가지게 됩니다. 기본값을 주지 않을 경우 0 부터 증가되는 값이 할당되게 됩니다. enum 숫자형의 경우 해당 숫자를 입력할 때도 에러가 없이 사용 가능하기 때문에 명시적인 안내를 주지는 못합니다.
enum Direction {
Up,
Down,
Left,
Right,
}
function setPosition(value: Direction) {}
setPosition(1) // 가능!
아래와 같이 enum에 문자열 값을 할당한 문자열 enum을 이용하거나 Union 타입을 이용하면 편집기의 피드백을 받을 수 있기 때문에 더 안전한 개발을 할 수 있습니다.
📎 Type Alias
객체 타입과 유니온 타입을 재사용 하거나 어떤 타입의 객체 이름을 붙혀줄 수 있습니다. Type Alias를 이용해 새로운 타입을 정의할 수 있습니다. 예를 들어 아까 객체 타입에서 사용한 코드에 타입 별칭을 붙힌다면 아래와 같이 의도를 전달할 수 있으며, 문서화 효과를 볼 수 있습니다.
type Point = {
x: number;
y: number;
}
function setPosition(pt: Point) {
console.log(pt.x, pt.y);
}
type Name = {
first: string;
last?: string;
}
function setName(name: Name) {}
type ID = number | string;
function printId(id: ID) {}
Type alias를 유용하게 사용하는 케이스 중 하나로 상태가 있습니다. 유저 상태, 로그인 상태 등등 프론트엔드 코드에서 상태에 따라 다른 UI를 보여주도록 분기타는 경우가 있는데, 이때 각 State의 타입을 만들어서 작성할 수 있습니다.
type SuccessState = {
result: 'success',
message: string,
}
type FailState = {
result: 'fail',
reason: string;
retry: boolean,
};
type ToastState = SuccessState | FailState;
function showToastMessage(state: ToastState) {
if (state.result == 'success') {
// 성공 시 메세지를 보여준다
} else {
// 실패 시 이유와 재시도 여부를 보여준다
}
}
Type 의 객체 구조가 다를 때 동일한 프로퍼티를 추가해서 분기를 타는 방법으로 작성하면 코드를 좀 더 간결하게 작성할 수 있습니다.
추가로 Type Alias는 확장성이 뛰어납니다. type을 & 키워드를 이용해 합치고, 프로퍼티를 다른 별칭으로 선언하는 등 재구성할 수 있습니다.
type UserInfo = {
id: number;
name: string;
age: number;
gender: 'male' | 'female';
address?: string
}
// 별도로 관리하지 않음으로써 수정 시 신경 써야하는 영역이 줄고 연관된 데이터의 결합도를 높혀줍니다.
type UserId = UserInfo['id']
type Gender = UserInfo['gender']
// Omit은 2번째 인자값을 제외하고, Pick은 2번째 인자값들만 꺼내 포함하게 됩니다.
type UserFormWithOmit = Omit<UserInfo, 'id'>;
type UserFormWithPick = Pick<UserInfo, 'name' | 'age' | 'gender' | 'address'>;
// & 를 이용해 합칠 수 있고 교집합됩니다.
type Book = {
title: string,
author: string,
}
type BookMetadata = {
author: string,
price: number,
}
type BookInfo = Book & BookMetadata;
객체와 마찬가지로 타입에서 index의 키값을 이용해 해당 타입을 꺼내올 수 있습니다.
위의 예제에서는 UserId, Gender 타입에 해당하며 동일한 타입을 바라보게 되고, 수정할 때 UserInfo만 수정하면 됩니다.
또한 Type Script 에서는 제공되는 유틸 타입을 이용해서 제외하거나, 추가하거나, 옵셔널로 변경하는 등의 작업을 할 수 있습니다.
Omit의 경우 타입에서 전달받은 인자값을 제외하고 반환하게 됩니다.
Pick의 경우 전달받은 인자값을 포함하고 반환하게 됩니다.
type Notice = {
id: number,
title: string,
description: string,
thumbnail: {
url: string,
alt: string
}
}
type NoticeReadOnly = Readonly<Notice>;
type NoticeOptional = Partial<Notice>;
TypeScript에서 제공하는 유틸타입을 통해 재구성도 가능합니다. 상세 페이지에서는 readonly 타입을 사용함으로써 추후 수정 시 리드온리 상태를 유지해야한다는 것을 명시할 수 있습니다. (좀 더 자세한 원리는 제네릭 편에서 함께 진행하겠습니다 :) )
📎 Interface
interface는 Typa alias와 유사하게 정의합니다. 객체의 구조를 정의하는게 사용되며 속성의 키와 타입, 옵셔널 여부 등을 명시할 수 있습니다. 변수, 함수, 클래스의 타입을 정의할 때 사용할 수 있습니다.
interface User {
name: string;
age: number
}
const user1: User = { name: "miffy", age: 70 }
인터페이스는 클래스와 동일하게 extends 키워드를 이용해서 상속이 가능합니다. 그리고 동일한 인터페이스를 중복으로 선언하게 되면 merge 가 됩니다.
interface Shape {
color: string;
}
interface Circle extends Shape {
radius: number;
}
const circle: Circle = { color: "red", radius: 10 };
📎 type과 interface의 차이점. 언제 써야할까?
인터페이스와 타입의 차이점을 먼저 살펴보면 아래와 같습니다.
특징 | Interface | Type |
사용 목적 | 주로 객체의 구조를 정의하는 데 사용 | 객체, 기본 타입, 유니언 타입, 튜플 등 다양한 타입을 정의할 수 있음 |
확장성 | 다른 인터페이스를 상속하거나 여러 인터페이스를 조합 가능 | & 연산자를 사용하여 다른 타입을 결합 가능 |
유연성 | 객체의 형태를 명확히 정의하는 데 적합 | 더 복잡하거나 유연한 타입 정의에 적합 |
중복 선언 | 동일한 이름의 인터페이스를 여러 번 선언하면 자동으로 병합됨 | 동일한 이름의 타입 별칭은 중복 선언 불가능 |
유니언 및 튜플 지원 | 지원하지 않음 | 지원함 |
컴파일러 출력 | Interface는 런타임에 사라짐 | Type도 컴파일 시 런타임에서 제거됨 |
// 1. 객체 정의
interface User {
name: string;
age: number;
}
type UserType = {
name: string;
age: number;
};
// 2. 확장
interface Admin extends User {
menus: Array<number>;
}
type AdminType = User & {
menus: Array<number>;
};
// 3. 유니언 타입 = type만 가능
type Result = "success" | "failure";
interface는 규격사양에 대한 공유 개념으로 정의해서 사용하곤 합니다. 계약서 처럼 객체 간 혹은 클래스에 대한 문서화로 사용하며, type은 어떤 데이터를 담을지 데이터의 타입을 결정할 때 사용하게 됩니다.
[Type Script] 객체, Union, enum, Type alias, interface
'Study-Note > TypeScript' 카테고리의 다른 글
[TypeScript] 개념과 특징, 기본 타입<원시값, 함수, 배열> (0) | 2025.01.13 |
---|