타입 스크립트에서 사용되는 데이터 타입들을 원시타입, 객체타입부터 타입스크립트에서만 제공되는 고급 타입까지 총 정리해보았습니다.
전반적인 타입스크립트의 계층 구조는 아래 사진을 참고해보시기 바랍니다.
1. 원시타입
number, string, boolean은 가장 대표적인 원시타입으로, 가장 많이 사용되고 사용자에 따라 다르게 사용될 여지가 적지만,
null 이나 undefined는 tsconfig 옵션이나 사용자의 취향에 따라서 다르게 사용될 여지가 많습니다.
1) boolean
true와 false 값만 할당할 수 있는 타입.
// boolean
let bool1 : boolean = true;
let bool2 : boolean = false;
2) undefined
정의되지 않았다는 의미의 타입. 초기화되어 있지 않거나 존재하지 않음을 나타냅니다.
// undefined 타입
let unde1: undefined = undefined;
3) null
자바스크립트에서 보통 빈 값을 할당해야 할 때 사용합니다. 자바스크립트에서 값이 없음을 나타날 때 null 과 undefined를 혼용하지만, 엄연히 따로 존재하는 원시값이므로 서로의 타입에 할당할 수 없습니다.
null과 undefined는 타입가드나 !(null이 아니라는 의미의 단언, 단언하고자 하는 변수 or 인수명 가장 오른쪽에 붙여 사용)를 통해 특정 상황에서 null과 undefined가 되는 경우를 걸러내기도 합니다.
// null
let null1: null = null;
null을 number 타입에 할당 가능할까 ?
let numA = null;
let numA: number = null; // ❌
null은 number에 포함되는 값이 아니므로, 오류가 발생합니다.
4) number
자바스크립트 숫자에 해당하는 모든 원시값을 할당할 수 있습니다. 정수, 부동소수점수를 구분하지 않으므로 모든 number 타입에 할당할 수 있습니다.
// number
let num1: number = 123;
let num2: number = -123;
let num3: number = 0.123;
let num4: number = -0.123;
let num5: number = Infinity;
let num6: number = -Infinity;
let num7: number = NaN;
5) bigInt
ES2020에서 새롭게 도입된 데이터 타입으로 타입스크립트 3.2버전부터 사용할 수 있습니다. 전보다 더 큰 수를 처리할 수 있도록 만들어진 타입입니다.
6) string
문자열을 할당할 수 있는 타입으로, 공백도 포함합니다. 작은 따옴표, 큰 따옴표로 둘러싸서 입력하며, 백틱(`)으로 감싼 문자열 내부에 변수값을 포함할 수 있는 템플릿 리터럴 문법도 있습니다.
// string
let str1: string = "hello";
let str2: string = 'hello';
let str3: string = `hello`;
let str4: string = `hello ${str1}`;
7) symbol
ES2015에서 도입된 타입이며, 어떤 값과도 중복되지 않는 유일한 값을 생성할 수 있습니다.
타입스크립트에는 symbol타입과 더불어 const 선언에서만 사용할 수 있는 unique symbol 타입이라는 symbol의 하위타입도 있습니다.
const MOVIE_TITLE = Symbol("title");
const MUSIC_TITLE = Symbol("title");
console.log(MOVIE_TITLE === MUSIC_TITLE) // false
※ 원시 값과 원시 래퍼 객체
자바스크립트에서는 내장 타입을 파스킬 표기법으로 표기했지만, 타입 스크립트는 이를 구분하고자 소문자로 표기합니다.
타입을 파스칼표기법으로 표기하면 자바스크립트에서는 이를 원시 레퍼 객체라고 부르는데, null과 undefined를 제외한 모든 원시값은 해당 원시값을 래핑한 객체를 가집니다.
1) 원시값
const str = "hello"; // string 원시값
const num = 123; // number 원시값
const bool = true; // boolean 원시값
2) 원시 래퍼 객체
const strObj = new String("hello"); // String 객체
const numObj = new Number(123); // Number 객체
const boolObj = new Boolean(true); // Boolean 객체
실제 개발 시에는 래퍼 객체를 직접 생성하는 것은 성능과 코드의 명확성, 예측 불가한 동작 방지, 메모리의 효율성을 위해 권장되지 않습니다.
2. 객체 타입
위에서 소개한 7가지 원시타입에 해당되지 않는 값은 모두 객체 타입으로 분류될 수 있습니다.
타입스크립트는 다양한 형태를 가지는 객체마다 개별적으로 타입 지정이 가능합니다.
1) object 가급적 사용 지양
object 타입은 any 타입과 유사하게 객체에 해당하는 모든 타입 값(객체, 배열, 정규 표현식, 함수, 클래스 등)을 유동적으로 할당할 수 있어 정적 타이핑의 의미가 크게 퇴색되기 때문에 가급적 사용하지 않는 것이 권장됩니다.
2) {} (중괄호)
자바스크립트에서 객체 리터럴 방식으로 객체를 생성할 때 사용됩니다.
타입스크립트에서 객체 타이핑 시에도 사용할 수 있는데, 중괄호 안에 객체의 속성 타입을 지정해주는 식으로 사용됩니다. {}만 사용할 경우 빈 객체임을 의미하며, {} 타입으로 지정된 객체에는 어떤 값도 속성으로 할당할 수 없습니다.
3) array
타입스크립트의 배열은 하나의 타입 값만 가질 수 있다는 점에서 자바스크립트 배열보다 조금 더 엄격합니다. 자바스크립트와 마찬가지로 원소 개수는 타입에 영향을 주지 않습니다. 선언 방법으로는 [] (대괄호)를 사용해서 선언하는 방법이나, Array 키워드로 선언하는 2가지 방식이 있습니다. (두 방식을 결과적으로 같기 때문에 둘 중 편한 것을 선택해 사용하면 됩니다.)
4) type과 interface
타입스크립트 object 타입은 실무에서는 잘 사용하지 않으며, type과 interface 가 흔히 객체를 타이핑하기 위해 자주 사용됩니다.
5) function
자바스크립트에서 함수를 function이라는 별도 타입으로 분류하듯, 타입스크립트에서는 함수를 별도 함수 타입으로 지정할 수 있습니다. 단, 아래 두 가지 사항을 주의해야 합니다.
- 타입스크립트는 function 이라는 키워드 자체를 타입으로 사용하지 않습니다.
- 타입스크립트에서는 함수의 매개변수도 별도 타입으로 지정해야합니다.
function add(a:number, b:number): number {
return a + b;
}
반면, 함수 자체의 타입은 호출 시그니처를 정의하는 방식으로 지정하면 됩니다.
타입스크립트에서 함수 자체 타입을 명시할 땐 화살표 함수 방식으로만 호출 시그니처를 정의합니다.
type add = (a:number, b: number) => number;
3. 고급 타입
고급 타입은 타입스크립트에만 존재하는 키워드지만, 그 개념은 자바스크립트에서 기인했습니다.
1) any 타입
자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있는 타입입니다. 타입스크립트는 동적 타이핑 특징을 가진 자바스크립트에 정적 타이핑을 적용하는 것이 주된 목적인 만큼, any를 사용하는 것은 지양하는 것이 좋습니다.
tsconfig 에서 noImplicityAny 옵션을 true로 설정하면, 타입이 명시되지 않은 변수의 암묵적인 any에 대한 경고를 발생시킬 수 있습니다. 다만, 상황에 따라 any타입을 어쩔 수 없이 사용해야할 때도 있는데 아래 3가지에 해당됩니다.
(1) 개발 단계에서 추후 값이 변경될 가능성이 있거나, 세부 항목에 대한 타입이 확정되지 않은 경우 임시로 값을 지정해야 할 때
(2) API 요청 및 응답 처리, 콜백 함수 전달, 타입이 잘 정제되지 않아 타입 파악이 힘든 외부 라이브러리 등을 사용할 때 어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때
(3) 외부 라이브러리나 웹 API 요청에 따라 다양한 값을 반환하는 API(대표적으로 Fetch API) 처럼, 값을 예측할 수 없을 때 암묵적으로 사용
2) unknown 타입
any와 유사하게 모든 타입 값이 할당될 수 있지만, any를 제외한 다른 타입으로 선언된 변수에는 unknown 타입 값을 할당할 수 없습니다. unknown은 어떤 연산에도, 어떤 메서드에서도 사용할 수 없고, 값을 저장하는 행위만 할 수 있습니다.
any타입과 비슷하지만, unknown 타입은 any타입으로 임시적으로 오류를 피한 뒤 특정타입을 수정해야하는 것을 깜빡하는 상황들을 보완하기 위해 등장한 타입입니다. 타입 검사를 강제하고 타입이 식별된 후 사용할 수 있기 때문에 any 보다 더 안전합니다. 따라서, 데이터 구조 파악이 힘들 경우 any 대신 unknown으로 대체해서 사용하는 방법이 권장됩니다.
아래와 같이 타입 좁히기를 통해 해당 값의 타입이 number 값임을 보장해주면 곱셈 연산 수행이 가능합니다.
if (typeof unknownVar === "number") {
// 이 조건이 참이된다면 unknownVar는 number 타입으로 볼 수 있음
unknownVar * 2;
}
3) void
자바스크립트에서는 함수에서 명시적인 반환문이 없다면, 기본적으로 undefined가 반환됩니다.
하지만, 타입스크립트에서는 void 타입이 사용됩니다. 변수에도 할당할 수 있긴 하지만, 함수가 아닌 값에 대해서는 대부분 무의미 합니다. void 타입으로 지정된 변수는 null 이나 undefined만 할당 가능합니다.
※ 함수 내부에 별도 반환문이 없다면 타입 스크립트 컴파일러가 알아서 함수 타입을 추론해주기 때문에 void 타입은 함수 자체를 다른 함수의 인자로 전달하는 경우가 아니라면, 잘 명시하지 않는 경향이 있습니다.
// 컴파일러가 자동으로 void로 추론
function log(message: string) {
console.log(message);
}
// 명시적으로 void 선언이 유용한 경우
type LogFunction = (message: string) => void;
const logger: LogFunction = (message) => {
console.log(message);
return true; // void 타입이므로 반환값은 무시됨
};
4) never
함수와 관련되어 많이 사용되는 타입으로, 값을 반환할 수 없는 타입을 의미합니다. 모든 타입의 하위 타입이며, 자신을 제외한 그 어떤 타입도 never 타입에는 할당될 수 없습니다.
값을 반환할 수 없는 경우는 크게 2가지로 구분됩니다. (반환하지 않는 것과 (void), 반환할 수 없는 것 (never))
1) 에러를 던지는 경우
2) 무한히 함수가 실행되는 경우
// 에러를 던지는 함수
function throwError(message: string): never {
throw new Error(message);
}
// 무한 루프 함수
function infiniteLoop(): never {
while (true) {
// ...
}
}
5) Array 타입
타입스크립트는 정적 타입의 특성을 살려 명시적인 타입을 선언해 해당 타입의 원소를 관리하는 것을 강제합니다.
배열 타입의 선언 방식은 다른 정적 언어와 유사하며, 자료형 + [] 형식을 사용해 선언합니다. 또는 Array 키워드를 사용해 제네릭이라는 문법을 사용해 선언 할 수도 있습니다.
결과적으로 아래와 같은 2가지 방식이 가능하며 선언 형식만 다른 것이기 때문에 혼용해서 사용해도 큰 문제는 없습니다.
const array: number[] = [1,2,3]; // 숫자에 해당하는 원소만 허용
const array: Array<number> = [1,2,3]; // 숫자에 해당하는 원소만 허용
튜플
이에 더해, 배열로부터 파생된 튜플이라는 타입은 대괄호 안에 타입을 직접 기술해 선언하며, 대괄호 안의 타입 개수가 곧 튜플이 가질 수 있는 원소의 개수를 의미합니다.
타입스크립트에서의 배열은 사전에 허용하지 않는 타입이 서로 섞이는 것을 방지함으로써 타입 안정성을 제공하며, 튜플은 이에 길이 제한까지 더해진 타입으로 원소 개수와 타입까지 보장합니다.
6) enum 타입
열거형이라고도 부르며, 타입스크립트에서 지원하는 특수한 타입입니다. enum은 주로 문자열 상수 생성에 사용됩니다.
열거형은 모든 멤버에 일일히 값을 할당할 수도 있으나, 직접 할당하지 않아도 값 할당이 누락된 멤버들에 대해 숫자 1을 기준으로 1씩 늘려가며 값을 자동으로 할당해 줍니다.
enum ProgrammingLanguage {
Typescript, // 0
Javascript, // 1
Java, // 2
Python, // 3
Kotlin, // 4
Rust, // 5
Go, // 6
}
ProgrammingLanguage.Typescript; // 0
ProgrammingLanguage["Go"]; // 6
ProgrammingLanguage[2]; // Java (역방향 접근도 가능)
enum ProgrammingLanguage {
Typescript = "Type",
Javascript = "Java",
Java = 300,
Python = 400,
Kotlin, // 401
Rust, // 402
Go, // 403
}
주로 문자열 상수 생성 시 사용되는 enum
enum은 관련이 높은 멤버를 모아 문자열 상수처럼 사용하고자 할 때 유용하게 쓸 수 있습니다.
enum은 그 자체로 변수 타입으로 설정 가능한데, enum이 타입으로 설정된 변수는 enum이 가지는 모든 멤버를 값으로 받을 수 있습니다.
아래 코드가 그 예시로, itemStatus는 ItemStatusType 열거형을 타입으로 가지는데, itemStatus가 문자열 타입으로 지정된 경우와 비교했을 때 아래와 같은 이점이 있습니다.
1) 타입 안정성 : checkItemAvailable는 ItemStatusType에 명시되지 않은 다른 문자열은 인자로 받을 수 없게 됩니다.
2) 명확한 의미전달 & 높은 응집력 : ItemStatusType이 다루는 값이 무엇인지 명확하고, 아이템 상태에 대한 값을 모아놓은 것으로 응집력이 좋아집니다.
3) 가독성 : ItemStatusType 내부 멤버를 통해 어떤 상태를 나타내는지 쉽게 이해할 수 있습니다.
📍 enum을 통해 문자열 상수를 설정해 사용한 경우
enum ItemStatusType {
DELIVERY_HOLD = "DELIVERY_HOLD", // 배송 보류
DELIVERY_READY = "DELIVERY_READY", // 배송 준비
DELIVERING = "DELIVERING", // 배송 중
DELIVERED = "DELIVERED", // 배송 완료
}
const checkItemAvailable = (itemStatus : ItemStatusType) => {
switch(itemStatus) {
case ItemStatusType.DELIVERY_HOLD:
case ItemStatusType.DELIVERY_READY:
case ItemStatusType.DELIVERING:
return false;
case ItemStatusType.DELIVERED:
default :
return true;
}
📍[참고] enum 없이 일반 문자열로 설정해 사용한 경우
// 문자열 리터럴 타입을 사용하는 경우
type ItemStatus = 'DELIVERY_HOLD' | 'DELIVERY_READY' | 'DELIVERING' | 'DELIVERED';
// 또는 그냥 string 타입을 사용하는 경우
const checkItemAvailable = (itemStatus: string) => {
switch(itemStatus) {
case 'DELIVERY_HOLD':
case 'DELIVERY_READY':
case 'DELIVERING':
return false;
case 'DELIVERED':
default:
return true;
}
}
// 이런 실수들이 가능해집니다:
checkItemAvailable('DELIVRY_HOLD'); // 오타지만 타입 에러가 발생하지 않음
checkItemAvailable('SHIPPED'); // 정의되지 않은 상태지만 허용됨
checkItemAvailable(''); // 빈 문자열도 허용됨
⚠️단, 열거형(enum) 사용 시 주의점 !
숫자로만 이루어져있거나, 자동으로 추론된 열거형은 역방향 접근이 가능하기 때문에 안전성이 떨어진다는 단점이 있습니다.
이러한 동작을 막기 위해 const enum으로 선언하는 방법이 있습니다. 하지만, 이 방법으로도 숫자 상수 방식으로 선언된 경우에 대해서 이 이외의 값을 할당하거나 접근하려고 할 때 방지할 수 없습니다.
따라서 const enum과 함께 문자열 상수 방식을 사용해 역방향 접근과 의도치 않은 값의 할당이나 접근을 방지하는 것이 비교적 안전합니다.
const enum NUMBER {
ONE = 1,
TWO = 2,
}
const myNumber : NUMBER = 100; // NUMBER enum 에 정의되지 않은 값이지만,
// 숫자형 enum은 컴파일 시점에 값이 인라인 처리되어 타입오류가 발생하지 않을 수 있다.
더 알아보기 ! enum과 const enum의 컴파일 차이
enum
// 일반 enum 컴파일 전
enum NORMAL_NUMBER {
ONE = 1,
TWO = 2
}
const normalValue = NORMAL_NUMBER.ONE;
// 컴파일 결과
var NORMAL_NUMBER;
(function (NORMAL_NUMBER) {
NORMAL_NUMBER[NORMAL_NUMBER["ONE"] = 1] = "ONE";
NORMAL_NUMBER[NORMAL_NUMBER["TWO"] = 2] = "TWO";
})(NORMAL_NUMBER || (NORMAL_NUMBER = {}));
const normalValue = NORMAL_NUMBER.ONE;
const enum
// const enum 컴파일 전
const enum NUMBER {
ONE = 1,
TWO = 2
}
const value = NUMBER.ONE;
// 컴파일 결과
const value = 1; // NUMBER.ONE이 직접 1로 대체됨
const enum은 실제 런타임에 enum 객체가 존재하지 않습니다