타입스크립트가 자체적으로 제공하는 타입들을 타입스크립트의 기본 타입이라고 부릅니다. 다른 말로 내장 타입이라고도 합니다.
이 각각의 기본 타입들은 서로 부모 자식 관계를 이루고 있습니다.
// 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;
자바스크립트에서의 숫자 타입입니다. 정수, 음수 뿐만 아니라 소수, Infinity
,-Infinity
, NaN
까지 포함됩니다.
// string
let str1: string = "hello";
let str2: string = 'hello';
let str3: string = `hello`;
let str4: string = `hello ${str1}`;
string
은 문자열 타입입니다. 쌍 따옴표, 작은 따옴표, 백틱, 템플릿 리터럴의 모든 문자열을 포함합니다.
// boolean
let bool1 : boolean = true;
let bool2 : boolean = false;
true
, false
를 포함하는 참, 거짓을 나타내는 타입입니다.
// undefined
let unde1: undefined = undefined;
// null
let null1: null = null;
undefined
와 null
은 자바스크립트의 원시 타입 그대로를 의미합니다.
자바스크립트에서 아직 값이 정해지지 않은 변수에 임시로 null
을 할당하여 사용하는 경우가 있습니다.
let numA: number = null; // ❌
하지만 타입스크립트에서는 위와 같이 number
타입의 변수에 null
을 할당할 수 없습니다.
이럴 때에는 strictNullChecks
옵션을 false
로 설정해주면 됩니다.
const sym1: symbol = Symbol('sym');
// typeof sym
let sym2: symbol = Symbol('sym');
// symbol
symbol
은 const
로 선언할 때와 let
으로 선언할 때의 타입 추론이 다릅니다.
타입스크립트에서는 const
로 선언한 변수의 타입이 symbol
일 경우 unique symbol
라고 합니다.
그리고 unique symbol
끼리는 비교가 불가능 합니다.
const symbol1 = Symbol.for('sym');
let symbol2 = Symbol.for('sym');
const big = 100000000n;
//const big: 100000000n
let big = 100000000n;
// let big: bigint
하나의 리터럴 값만 할당가능하도록 하는 리터럴 타입의 변수도 선언할 수 있습니다.
let numA: 10 = 10;
let strA: "hello" = "hello";
let boolA: true = true;
let boolB: false = false;
위와 같이 변수를 타이핑하면 말 그대로 이 리터럴 값만 할당할 수 있는 리터럴 타입 변수가 됩니다.
let
으로 선언하지 않고 const
로 선언하면 굳이 리터럴 타입 변수로 변수를 타이핑 하지 않아도 리터럴 타입의 변수로 알아서 타입스크립트가 추론합니다.
any 타입은 타입 검사를 받지 않는 타입입니다.
any 타입으로 변수를 타이핑하는 경우는 권장하는 방법이 아닙니다. 변수를 any타입으로 타이핑하면 타입스크립트를 사용하는 이점을 포기하게 되는 것이기 때문입니다.
let anyVar = 10;
anyVar = "hello"; // 오류 발생!
위와 같이 anyVar
변수를 타이핑 하지 않으면, 10을 할당했기 때문에 타입스크립트가 anyVar
의 타입을 number
타입으로 추론합니다.
타입스크립트는 초기화 된 값의 타입을 기준으로 변수의 타입을 추론하기 때문입니다.
따라서 이 다음에 anyVar
변수에 string
타입의 값인 "hello"
를 할당하면 오류가 발생하게 됩니다.
let anyVar: any = 10;
anyVar = "hello";
anyVar = true;
anyVar = {};
anyVar.toUpperCase();
anyVar.toFixed();
anyVar.a;
anyVar
변수를 any
타입으로 타이핑 하게되면 아무 타입의 값이나 변수에 할당할 수 있게됩니다.
toUpperCase()
와 toFixed()
같은 string
, number
타입의 메서드를 호출해도 문제가 발생하지 않습니다.
하지만 이 코드를 ts-node
로 실행하거나 컴파일을 하게되면 런타임 오류가 발생하게 됩니다.
any
타입과 비슷하지만 보다 안전합니다.
unknown
타입 변수에는 어떤 타입의 값이든 저장할 수 있습니다.
let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};
하지만 unknown
타입 변수의 값을 다른 타입 변수에 할당할 수 없습니다.
let num: number = 10;
(...)
let unknownVar: unknown;
unknownVar = "";
unknownVar = 1;
unknownVar = () => {};
num = unknownVar; // 오류 !
또한 unknown
타입 변수는 어떤 연산에도 참여할 수 없고, 어떤 메서드도 사용할 수 없습니다.
let unknownVar: unknown;
(...)
unknownVar * 2 // 오류!
void
타입은 아무런 값도 없음을 의미하는 타입입니다.
let a: void;
a = undefined;
void
타입의 변수에는 undefined
타입 값을 저장할 수 있지만 그 외의 값은 저장할 수 없습니다.
아무 값도 반환하지 않은 함수의 반환값 타입을 정의할 때도 사용합니다.
function func2(): void {
console.log("hello");
}
never
타입은 불가능을 의미하는 타입입니다.
함수가 어떠한 값도 반환할 수 없거나, 오류를 던지는 경우 반환값을 never
타입으로 정의할 수 있습니다.
function func3(): never {
while (true) {}
}
function func4(): never {
throw new Error();
}
배열은 자바스크립트에서의 배열과 다르지 않습니다.
let numArr: number[] = [1, 2, 3]
위와 같이 배열 타입의 변수를 선언하고 배열을 할당합니다.
let boolArr: Array<boolean> = [true, false, true];
위와 같은 방법으로도 boolean 배열 타입 변수를 선언할 수 있습니다.
let multiArr: (number | string)[] = [1, "hello"];
이렇게 다양한 타입의 값을 갖는 배열 타입 변수도 선언할 수 있습니다.
튜플은 길이와 타입이 고정된 배열을 의미합니다.
let tup1: [number, number] = [1, 2];
let tup2: [number, string, boolean] = [1, "hello", true];
위와 같이 튜플 타입의 변수를 선언할 수 있습니다.
튜플도 결국 배열이기 때문에 push
, pop
같은 메서드 사용이 가능합니다.
타입스크립트에서 객체 타입의 변수를 선언할 때 object
타입으로 선언할 수 있습니다.
let user: object = {
id: 1,
name: "정민교",
};
user.id;
하지만 이렇게 선언하게 되면 타입스크립트는 오류를 발생시킵니다.
object
타입에 id
프로퍼티가 존재하지 않는다는 에러입니다.
타입스크립트의 object
타입은 이 타입이 object
라는 것 외에는 어떠한 정보도 포함하지 않습니다.
따라서 타입스크립트에서 객체 타입의 변수를 정의하기 위해서는 객체 리터럴 타입으로 정의하는 것이 좋습니다.
let user: {
id: number;
name: string;
} = {
id: 1,
name: "정민교",
};
user.id;
이렇게 객체 리터럴 문법과 비슷한 방식으로 타입을 정의한다고 하여 객체 리터럴 타입이라고 합니다.
이렇게 변수의 타입을 객체 리터럴 타입 방식으로 정의하면 정상적으로 user.id
의 값을 참조할 수 있습니다.
객체 타입에서 특정 프로퍼티가 있어도 되고 없어도 되도록 객체 타입을 정의할 수 있습니다.
let user: {
id?: number; // 선택적 프로퍼티가 된 id
name: string;
} = {
id: 1,
name: "정민교",
};
user = {
name: "홍길동",
};
위와 같이 타입 정의의 프로퍼티 뒤에 ?
를 붙이면 선택적(Optional) 프로퍼티가 됩니다.
선택적 프로퍼티는 말 그대로 있어도 되고 없어도 되지만 있다면 위에서 보이듯이 반드시 number
타입이어야 합니다.
읽기 전용 프로퍼티로 정의할 수도 있습니다.
특정 프로퍼티 앞에 readonly
키워드를 붙이면 읽기 전용 프로퍼티가 됩니다.
let user: {
id?: number;
readonly name: string; // name은 이제 Readonly 프로퍼티가 되었음
} = {
id: 1,
name: "정민교",
};
user.name = "dskfd"; // 오류 발생
위와 같이 값을 할당한 이후에는 변경이 불가능합니다.
// enum 타입
// 여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입
enum Role {
ADMIN, // 0 할당(자동)
USER, // 1 할당(자동)
GUEST, // 2 할당(자동)
}
const user1 = {
name: "정민교",
role: Role.ADMIN, // 0
};
const user2 = {
name: "홍길동",
role: Role.USER, // 1
};
const user3 = {
name: "아무개",
role: Role.GUEST, // 2
};
위와 같은 방식으로 enum
타입을 정의할 수 있습니다.
직접 enum 멤버에 값을 할당할 수 있지만, 할당하지 않으면 순서대로 숫자값이 할당됩니다.
var Role;
(function (Role) {
Role[Role["ADMIN"] = 0] = "ADMIN";
Role[Role["USER"] = 1] = "USER";
Role[Role["GUEST"] = 2] = "GUEST";
})(Role || (Role = {}));
enum
타입은 컴파일하면 객체가 됩니다.
타입스크립트에서는 타입 별칭을 이용해서 별도의 타입을 정의할 수 있습니다.
아래와 같이 User
라는 타입을 우리가 직접 정의하여 사용할 수 있습니다.
// 타입 별칭
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
정의한 타입 별칭을 이용하여 변수의 타입을 정의할 수 있습니다.
let user2: User = {
id: 2,
name: "홍길동",
nickname: "winterlood",
birth: "1997.01.07",
bio: "안녕하세요",
location: "부천시",
};
같은 스코프 안에서 동일한 이름의 타입 별칭을 선언할 수 없습니다.
다른 스코프에서는 가능합니다.
// 불가능
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
type User = {}
// 가능
type User = {
id: number;
name: string;
nickname: string;
birth: string;
bio: string;
location: string;
};
function test() {
type User = string;
}
인덱스 시그니처는 객체 타입을 유연하게 정의할 수 있도록 도와주는 문법입니다.
type CountryCodes = {
Korea: string;
UnitedState: string;
UnitedKingdom: string;
};
let countryCodes: CountryCodes = {
Korea: "ko",
UnitedState: "us",
UnitedKingdom: "uk",
};
국가마다 영어 코드를 저장해야 해서 위와 같은 타입을 만들었습니다.
그런데 이렇게 타입을 정의하면 국가가 추가될 때마다 계속 프로퍼티를 추가해주어야 합니다.
이럴 때 인덱스 시그니처를 이용하면 유연하게 객체 타입을 정의할 수 있습니다.
type CountryCodes = {
[key: string]: string;
};
let countryCodes: CountryCodes = {
Korea: "ko",
UnitedState: "us",
UnitedKingdom: "uk",
// (... 약 100개의 국가)
Brazil : 'bz'
};
ContryCodes
타입은 key가 string
타입이고, 값이 string
타입을 가지는 모든 프로퍼티를 포함하는 타입임을 정의한 것입니다.
국가 코드를 숫자로 보관하는 객체가 하나 더 필요하면 타입을 하나 더 정의하면 됩니다.
type CountryNumberCodes = {
[key: string]: number;
};
반드시 포함해야하는 프로퍼티가 있다면 직접 명시할 수도 있습니다.
type CountryNumberCodes = {
[key: string]: number;
Korea: number;
};
하지만 추가적인 프로퍼티를 직접 명시할 때는 인덱스 시그니처의 value 타입과 직접 추가한 프로퍼티의 value 타입이 호환되어야 합니다.
type CountryNumberCodes = {
[key: string]: number;
Korea: string; // 오류!
};