
이 글은 이정환 강사님의 한 입 크기로 잘라먹는 타입스크립트(TypeScript) 강의를 참고하여 개념을 정리했습니다!
0. 기본 타입이란?
TypeScript의 기본 타입(Basic Types)은 언어가 기본적으로 제공하는 내장 타입을 의미합니다.
number,string,boolean,null,undefined등이 있고any,void,never,unknown처럼 TypeScript에서만 제공되는 타입들도 있습니다.
이 타입들은 단순히 나열된 것이 아니라, 서로 부모–자식 관계를 이루는 계층 구조를 가지고 있습니다.
1. 원시 타입과 리터럴 타입
원시 타입 (Primitive Type)
- 한 번에 하나의 값만 담을 수 있는 기본 타입입니다.
객체나 배열처럼 여러 값을 동시에 저장하는 구조가 아닙니다.number,string,boolean등이 포함됩니다.
타입 주석(Type Annotation)
변수 뒤에 :을 붙여 타입을 명시합니다.
let num: number = 123;
number
number 타입은 정수, 소수, 음수, Infinity, NaN 등 숫자 관련 모든 값을 포함합니다.
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; // Not a Number
- number 타입 변수에는 숫자만 할당할 수 있고,
문자열 전용 메서드는 사용할 수 없습니다.
num1 = 'hello'; // 타입 불일치
num1.toUpperCase(); // number 타입에 없는 메서드
string
문자열은 ", ', 백틱(\)을 모두 사용할 수 있으며, 템플릿 리터럴(${})도 가능합니다.
let str1: string = "hello";
let str2: string = 'hello';
let str3: string = hello;
let str4: string = hello ${str1};
boolean
- 참(true), 거짓(false) 두 가지 값만 가질 수 있습니다.
- 흔히 조건문이나 비교 연산 결과를 저장할 때 사용합니다.
let bool1: boolean = true;
let bool2: boolean = false;
null
null타입은 값이 없음을 명시적으로 표현할 때 사용합니다.- 오직
null하나의 값만 포함합니다.
let null1: null = null;
undefined
undefined타입은 값이 정의되지 않았음을 의미한다.null타입과 마찬가지로 오직undefined하나의 값만 가진다.let undefined : undefined = undefined;
null과 undefined 비교
- 공통점
- 둘 다 “값이 없다”는 걸 표현하는 타입입니다.
- 실제 데이터가 존재하지 않는 상태를 나타낸다는 점에서 같습니다.
- 차이점 (개발자의 의도)
null: 개발자가 직접 비워둔 값. “지금은 비어있지만 나중에 채울 거야”라는 의도가 담겨 있습니다.undefined: 자동으로 비워진 값. 변수는 만들어졌지만 아직 어떤 값도 안 들어간 상태입니다.
리터럴 타입(Literal Type)
- 값 그 자체가 타입이 되는 형태를 리터럴 타입이라고 합니다.
- 딱 하나의 값만 허용하는 타입 입니다.
let numA: 10 = 10;
- 리터럴 타입은 숫자뿐 아니라 문자열, 불리언 값에도 적용됩니다.
let strA: "hello" = "hello";
let boolA: true = true;
let boolB: false = false;
"hello"라는 특정 문자열,true나false라는 특정 불리언 값 자체를 하나의 고정된 타입으로 다루는 개념입니다.
2. 배열과 튜플
배열(Array)
- 같은 타입의 값들을 묶어서 저장하는 자료형
- 배열 타입을 정의하는 기본적인 방법
- 변수 이름 뒤에 :로 타입 주석을 쓰고,
요소타입[]형태로 정의합니다.
- 변수 이름 뒤에 :로 타입 주석을 쓰고,
let strArr: string[] = ["hello", "im", "winterlood"];
let numArr: number[] = [1, 2, 3];
- 또는 제네릭 문법으로 같은 의미로 표현할 수도 있습니다.
let boolArr: Array<boolean> = [true, false, true];
- 두 방법은 완전히 동일하지만, 대부분은
타입[]방식이 간결해서 더 자주 쓰입니다다! - 같은 타입의 값들을 묶어 저장합니다.
let strArr: string[] = ["a", "b", "c"];
let boolArr: Array = [true, false];
number[]와Array는 동일한 의미이지만, 전자가 더 일반적입니다.
보충 개념 : 제네릭(Generic)
- 타입을 변수처럼 사용하는 문법입니다.
- 함수나 클래스, 타입을 만들 때
구체적인 타입을 미리 정하지 않고, 사용할 때 원하는 타입을 전달해서 재사용성을 높이는 방법
function printArray(arr: T[]): void {
console.log(arr);
}
printArray([1, 2, 3]);
printArray(["a", "b", "c"]);
- 여기서
<T>가 바로 제네릭 타입 변수 - 이 함수는 배열을 출력하지만, 배열 안의 타입이
string일 수도,number일 수도 있습니다다. - 그래서
<T>라는타입 자리를 미리 만들어 두고, 함수를 쓸 때 원하는 타입을 넣습니다.
printArray<number>([1, 2, 3]); // number[]
printArray<string>(["a", "b", "c"]); // string[]
- 핵심 : 하나의 함수로 여러 타입을 처리!
여러 타입을 허용하는 배열
유니온 타입(|)을 사용해 여러 타입의 요소를 허용할 수 있습니다.
let multiArr: (number | string)[] = [1, "hello"];
- 괄호 안의
number | string은 배열의 각 요소가 숫자이거나 문자열일 수 있음을 의미합니다.
→ 즉, 여러 타입 중 하나를 허용하는 타입이 됩니다.
다차원 배열
[]를 연달아 쓰면 다차원 배열도 표현할 수 있습니다.
let doubleArr: number[][] = [
[1, 2, 3],
[4, 5],
];
튜플(Tuple)
- 배열과 비슷하지만 길이와 각 요소의 타입이 고정된 배열입니다.
let tup1: [number, string, boolean] = [1, "hello", true];
- 첫 번째 요소는 숫자, 두 번째는 문자열, 세 번째는 불리언처럼 각 자리에 어떤 타입이 올지 정확히 정해진 배열입니다.
튜플을 사용할 때 주의점
- 튜플도 결국 자바스크립트에서는 배열로 변환되기 때문에,
push나pop같은 배열 메서드를 사용하면 길이가 변할 수 있습니다.
let tup1: [number, number] = [1, 2];
tup1.push(3); // 가능하지만 의도치 않은 결과
- 튜플을 쓸 때는 배열 메서드로 값을 추가하거나 삭제하지 않도록 주의해야 합니다.
튜플 사용 예시
- 튜플을 사용하면, 배열의 구조(요소 타입과 순서) 까지 타입으로 안전하게 관리할 수 있습니다!
- 예시 : 회원 정보를 이름과 ID로 구성된 배열로 관리
const users = [
["개냥이", 1],
["이아무개", 2],
["김아무개", 3],
["박아무개", 4],
];
- 이때 누군가 순서를 잘못 입력하면, 문제가 발생합니다.
["조아무개", 5]; // 올바름
[5, "조아무개"]; // 순서 잘못됨
- 자바스크립트는 이런 실수를 잡지 못하지만, TypeScript에서는
튜플 타입으로 지정해 방지할 수 있습니다.
const users: [string, number][] = [
["개냥이", 1],
["이아무개", 2],
[5, "조아무개"], // 오류
];
3. 객체
객체 타입으로 정의하는 방법
TypeScript에서 객체 타입을 정의하는 방법은 크게 두 가지가 있습니다!
1. object 타입
let user: object = {
id: 1,
name: "개냥이",
};
- 이렇게 하면
user가 객체라는 건 알려주지만, 객체 안에 어떤 프로퍼티가 있는지는TypeScript가 알 수 없습니다. - 그래서 아래처럼 프로퍼티에 접근하면 오류가 발생합니다.
user.id; // 오류: 'object' 타입에는 'id' 프로퍼티가 없습니다.- 결론 :object 타입은 “이건 객체임” 정도만 알려주고 구체적인 구조 정보는 전혀 제공하지 않습니다.
2. 객체 리터럴 타입(Object Literal Type)
- 다음처럼 객체 리터럴 타입으로 정의할 수 있습니다.
let user: {
id: number;
name: string;
} = {
id: 1,
name: "개냥이",
};
user.id; // 정상 작동
- 이제
user가 어떤 프로퍼티를 갖고 있는지 TypeScript가 정확히 알 수 있습니다.
->user.id,user.name처럼 안전하게 접근할 수 있습니다.
구조적 타입 시스템(Structural Type System)
- TypeScript는 객체의 구조(프로퍼티 구성)로 타입을 판단합니다.
- “이 객체에 name과 color가 있다면, 이건 강아지 타입이야” 라는 식으로 판단합니다.
let dog: { name: string; color: string; } = { name: "돌돌이", color: "brown", };
특수한 프로퍼티 정의하기
- 객체 타입을 정의할 때, 프로퍼티를 선택적으로(
optional) 또는 읽기 전용(readonly) 으로 지정할 수도 있습니다.
1. 선택적 프로퍼티(Optional Property)
- 프로퍼티 이름 뒤에
?를 붙이면, 그 프로퍼티는 있어도 되고 없어도 되는 선택적 속성이 됩니다.
let user: {
id?: number;
name: string;
} = {
name: "홍길동"
};
- 단, 존재할 경우엔 정해진 타입만 허용됩니다.
user = {
id: "id", // 오류 (id는 number 타입이어야 한다)
name: "홍길동",
};
2. 읽기 전용 프로퍼티(Readonly)
readonly를 붙이면 한 번 정해진 값은 수정할 수 없습니다.- 이러한 방식으로 실수로 데이터를 바꾸는 걸 방지할 수 있습니다.
let user: { readonly name: string; } = { name: "개냥이", };
user.name = "홍길동"; // 오류
4. 타입 별칭(Type Alias)과 인덱스 시그니처(Index Signature)
타입 별칭
- 긴 타입을 간단한 이름으로 저장해두는 문법
- 변수 선언하듯
type키워드로 새 타입을 정의 - 장점 : 반복되는 구조를 짧게 쓰고, 가독성도 훨씬 좋아집니다!
type User = {
id: number;
name: string;
nickname: string;
birth: string;
};
- 이제
User라는 이름만 써도 이 객체 타입 전체를 대체할 수 있습니다.
let user: User = { ... };
let user2: User = { ... };
주의 : 타입 별칭 이름은 중복 금지!
- 변수처럼 같은 스코프(범위) 안에서는 같은 이름 중복 선언 불가합니다.
type User = { id: number };
function test() {
type User = string; // 가능
}
- 결론 : 함수 안의
User는 바깥의User와 별개니까 괜찮습니다.
타입 별칭은 컴파일하면 사라짐
- 타입 별칭은 개발 중 타입 검사 용도로만 사용됩니다.
- 컴파일 후, 자바스크립트 결과물에는 전혀 남지 않습니다.
type User = { id: number };
- 이 코드는 컴파일 후 완전히 사라지고, 실제 JS 코드에는 아무 영향도 없다.
인덱스 시그니처
- 객체의 key/value 타입을 유연하게 지정할 수 있게 해주는 문법입니다.
- 예시 : 전 세계 국가 코드를 담은 객체
type CountryCodes = {
[key: string]: string;
};
let countryCodes: CountryCodes = {
Korea: "ko",
UnitedState: "us",
Brazil: "br",
};
- 모든
key는string, 모든value도string이라는 의미 - 국가가 10개든, 100개든, 일일이 다 정의하지 않아도 됩니다!
- 숫자 값이 필요한 경우엔?
type CountryNumberCodes = {
[key: string]: number;
};
let codes: CountryNumberCodes = {
Korea: 82,
Japan: 81,
};
인덱스 시그니쳐 + 필수 프로퍼티 같이 쓰기
- 인덱스 시그니처에 더해서 특정
key는 무조건 있어야 한다고 명시할 수 있다.
type CountryNumberCodes = {
Korea: number; // 필수
};
- 주의점 : 단, 인덱스 시그니처의
value타입과 필수 프로퍼티의 타입이 달라지면 오류가 발생
type CountryNumberCodes = {
[key: string]: number;
Korea: string; // 오류
};
5. 열거형(Enum)
enum은 TypeScript에서만 제공되는 특별한 타입- 여러 개의 이름 있는 상수 값을 한 곳에 모아 관리할 때 사용합니다.
JS에는 없는 문법이지만, 컴파일하면 실제JS객체로 바뀝니다.기본 문법- 예시
enum Role {
ADMIN,
USER,
GUEST,
}
- 이렇게 선언하면
Role.ADMIN,Role.USER,Role.GUEST세 개의 상수를 가진enum이 만들어집니다. - 기본적으로 값은 0부터 자동으로 1씩 증가합니다.
Role.ADMIN→ 0Role.USER→ 1Role.GUEST→ 2
- 직접 숫자 값 지정하기
enum Role {
ADMIN = 10,
USER, // 11 (자동 증가)
GUEST, // 12
}
- 맨 위 값만 직접 지정하면, 그 아래 멤버들은 자동으로
+1씩 증가한 숫자가 할당된다. - 이런 형태를 숫자형 enum (Numeric Enum) 이라고 부른다.
- 문자열 열거형 (String Enum)
enum의 값은 꼭 숫자일 필요는 없다. 문자열도 지정할 수 있습니다.- 언어 코드 같은 고정 문자열을 안전하게 관리할 때 자주 씁니다.
enum Language {
korean = "ko",
english = "en",
}
- 실제 사용 예시
enum을 사용하면 숫자나 문자열 대신 의미 있는 이름으로 값들을 다룰 수 있어서 코드가 훨씬 읽기 쉬워집니다.
enum Role {
ADMIN,
USER,
GUEST,
}
enum Language {
korean = "ko",
english = "en",
}
const user1 = {
name: "이정환",
role: Role.ADMIN, // 0
language: Language.korean, // "ko"
};
- 컴파일 결과
enum은TypeScript에서 컴파일될 때 사라지지 않고, JS 객체 형태로 변환됩니다.- 그래서
enum을 타입뿐 아니라 값으로도 사용할 수 있습니다!
var Role;
(function (Role) {
Role[Role["ADMIN"] = 0] = "ADMIN";
Role[Role["USER"] = 1] = "USER";
Role[Role["GUEST"] = 2] = "GUEST";
})(Role || (Role = {}));
- 이런 식으로 양방향 매핑 객체로 변환됩다.
Role.ADMIN→0Role[0]→"ADMIN"
6. any와 unknown
any 타입
TypeScript에서만 제공되는 타입 검사를 아예 안 받는 치트키 타입.any타입을 쓰면TypeScript의 모든 타입 규칙이 무시됩니다.
let anyVar: any = 10;
anyVar = "hello";
anyVar = true;
anyVar = {};
anyVar.toUpperCase();
anyVar.toFixed();
anyVar.a;
- 어떤 값이든 담을 수 있고, 어떤 메서드를 호출해도 오류가 나지 않습니다.
- any는 모든 타입의 슈퍼타입이라서 다른 어떤 타입 변수에도 자유롭게 할당됩니다.
let anyVar: any = "hello";
let num: number = 10;
num = anyVar; // 가능 <- (타입 검사 통과)
any 타입의 문제점
TypeScript의 타입 안전성을 완전히 잃게 되고, 런타임 오류 위험이 커집니다.any는 진짜 어쩔 수 없는 경우를 제외하고는 사용하지 않는 게 원칙입니다!
unknown 타입
unknown은any처럼 모든 타입의 값을 담을 수는 있습니다.- 차이점 :
any와는 다르게, 아무 곳에도 자유롭게 할당할 수는 없는 보다 안전한 타입입니다!
let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};
- 어떤 값이든 받을 수 있지만, 그걸 다른 타입 변수에 넣을 때는 오류가 발생합니다.
let num: number = 10;
num = unknownVar; // 오류
unknown타입의 값은 연산을 하거나 메서드를 직접 호출할 수도 없습니다.
unknownVar * 2; // 오류
unknownVar.toUpperCase(); // 오류
- 결론
unknown은 “받을 수는 있지만 쓸 수는 없는 타입” 입니다.- 이 값이 어떤 타입인지 확실히 보장되기 전까지는 사용할 수 없습니다.
타입 좁히기(Type Narrowing)
- 만약
unknown값을 실제로 사용하려면, 조건문으로 타입을 먼저 확인해줘야 합니다. - 이런 식으로 조건을 통해 타입을 좁혀주는 과정을 타입 좁히기(Type Narrowing)라고 합니다.
if (typeof unknownVar === "number") {
unknownVar * 2; // 이제 number로 인식됨
}
7. void와 never
void 타입
void는 “아무 값도 없다” 는 걸 의미하는 타입입니다.- 주로 함수가 아무 값도 반환하지 않을 때 사용합니다.
function func2(): void {
console.log("hello");
}
- 이 함수는 `console.log`만 실행하고 아무것도 반환하지 않기 때문에 반환 타입을 void로 지정한다.
변수에 void 타입 지정하기
void타입 변수에는undefined만 담을 수 있습니다.
let a: void;
a = undefined;
- 단,
tsconfig.json에서strictNullChecks: false로 설정하면null도 허용됩니다.
// strictNullChecks: false 일 경우
let a: void;
a = undefined;
a = null;
never 타입
- never는 값이 존재할 수 없음 을 뜻합니다.
- 절대로 반환되지 않거나 실행이 끝나지 않는 코드를 표현할 때 사용합니다.
function func3(): never {
while (true) {} // 무한 루프 → 절대 끝나지 않음
}
- 오류를 던져서 함수가 강제로 종료되는 경우도 never 타입이다.
function func4(): never {
throw new Error("Something went wrong");
}
- 이 함수도 값을 반환할 수 없으니 never로 지정합니다.
변수에 never 타입 지정하기
- never는 그 어떤 값도 담을 수 없는 타입입니다.
any타입의 값조차도 들어갈 수 없습니다.
let a: never;
a = 1; // 오류
a = null; // 오류
a = undefined; // 오류
마무리
이 글에서는 TypeScript의 기본 타입을 모두 정리해 보았습니다.
- number, string, boolean, null, undefined
- 배열(Array), 튜플(Tuple)
- 객체(Object Literal Type)
- 타입 별칭(Type Alias), 인덱스 시그니처(Index Signature)
- enum, any, unknown, void, never
다음 글에서는 Section 03, 타입스크립트의 타입 이해하기를 정리할 예정입니다!
