타입스크립트를 다시 공부하면서, 타입부터 정리해보려고 한다.
원시 타입은, 하나의 값만 저장 하는 타입으로, number string boolean null undefined 가 있다.
모든 숫자 값을 나타내는 타입이다.
Infinity -Infinity NaN 도 number 타입에 포함된다.
let num1 : number = Infinity;
let num2 : number = -Infinity;
let num3 : number = NaN;
// 다른 타입에서만 사용할 수 있는 메서드는 사용하면 에러가 난다.
num1.toUpperCase() // error!
문자열을 의미하는 타입이다.
당연하게도, 백틱을 이용한 템플릿 리터럴도 포함한다.
let str1: string = `hi ${num1}`;
true와 false 만 저장할 수 있다.
null 값 이외에는 담을 수 없다.
undefined 값 이외에는 담을 수 없다.
리터럴 타입 이란, 고정된 값 만을 허용하는 타입이다.
let num: 10 = 10; // 10만 허용할 것임.
num = 20; // error!!
let strA: 'hello' = 'hello';
strA = 'hi'; // error!!
이게 대체 어디에 쓰이겠냐 싶겠지만, 이후에 복합적인 타입을 만들 때 유용하니까 알아두자.
같은 타입의 여러 값을 순서대로 저장하는 자료형이다.
let strArr: string[] = ["hi", "there"];
let boolArr: Array<boolean> = [true, true, true]; // 제네릭으로 나타낼 수 있다.
let multiArr1: (string | number)[] = [1, "hello"];
let multiArr2: Array<string | boolean> = [true, "hello"]; // 멀티 타입도 가능
let doubleArr: number[][] = [[1, 2],[3, 4]]; // 다차원도 가능
길이와 타입이 고정된 배열이다.
자바스크립트에는 없고, 타입스크립트에서만 특별히 제공된다.
let tup1: [boolean, boolean] = [true, false]; // 선언
tup1 = [1, false] // error!! boolean 타입만 가능
tup1 = [false, false, false] // error!! 길이 초과
튜플은 타입스크립트에서만 특별히 제공된다고 했는데, 실제로 JS로 컴파일되어 변환될 때는 결국 배열로 컴파일된다.
그렇기 때문에, 실제 배열처럼 push() 나 pop() 메서드를 사용할 수 있다.
두 메서드는 배열의 길이에 영향을 주게 되는데, 그렇다면 에러가 날까?
tup1.push(false); // not error
놀랍게도, 타입스크립트가 감지하지 못한다.
따라서, 튜플 타입을 사용할 때는 push() 나 pop() 을 각별히 주의해서 사용해야 할 필요가 있겠다.
물론, 두 메서드를 사용할 때 내부 타입은 제대로 검사해주고, 길이 가 달라지는걸 캐치하지 못할 뿐이다.
튜플은 어디에 사용할 수 있을까?
예를 들어, 유저 정보와 나이를 배열로 관리한다고 해보자.
const users = [
['Alice', 21],
['Bob', 22],
];
// 유저 정보를 추가할 것임.
users.push(22, 'leedo'); // error!! 인덱스 위치 불일치
위와 같이, 인덱스의 순서와 위치가 헷갈릴 수 있는 상황을 막아준다.
객체를 나타내는 object 타입과, 객체 리터럴 타입이 존재한다.
let user: object = {
id: 1,
name: "leedo",
};
console.log(user.id) // error!!;
언뜻 보면 이 코드는 에러가 나지 않을 것 같지만, object 타입은 단순히 객체 라는 정보만을 주기 때문에, 타입스크립트가 user 안에 어떤 속성이 있는지 감지하지 못하고, 에러가 발생한다.
따라서, 아래와 같이 객체 리터럴 타입을 사용해야 한다.
구체적인 객체의 구조와 속성을 정의하는 타입이다.
객체가 가져야 할 속성 과 속성의 타입 을 지정하는 방법이다.
let user: {
readonly id: number;
name: string;
option?: string;
} = {
id: 1,
name: "leedo",
};
console.log(user.id) // leedo
user.id = '길동이'; // error!! readonly
선택적 프로퍼티(Optional property)를 사용해, 있어도 되고, 없어도 되는 속성으로 지정해줄 수도 있다.
readonly 키워드를 사용해, 읽기 전용 속성으로 만들어줄 수도 있다.
enum(Enumeration : 열거형) 타입은 관련된 상수 값들을 하나로 묶어서, 이름을 부여해 사용할 수 있게 해주는 타입이다.
만약, 특정 User가 존재하고, Role 이라는 속성 값을 가진다고 해보자.
const user1 = {
role: 0, // ADMIN
}
const user2 = {
role: 1, // USER
}
const user1 = {
role: 2, // GUEST
}
이렇게 0은 어드민을, 1은 일반 유저를, 2는 게스트를 나타내기 위해 사용했다고 했다고 가정하자.
과연 몇 개월, 몇 년이 지난 후에 저 코드를 봤을 때, 저 숫자들이 무엇을 의미하는지 알 수 있을까?
이런 헷갈릴 수 있는 상황을 enum을 통해 방지할 수 있다.
enum Role {
ADMIN = 0,
USER = 1,
GUEST = 2,
}
const user1 = {
role: Role.ADMIN,
}
const user2 = {
role: Role.USER,
}
const user1 = {
role: Role.GUEST,
}
이런식으로 사용할 수 있다.
주석 없이도 이해할 수 있는 코드가 좋은 코드라고 배웠고, 이렇게하면 주석 없이도 해당 코드를 이해하는데 어려움이 들지 않을 것이다.
any 타입과 Unknown 타입은 변수에 저장할 값이 확실하지 않을 때 활용할 수 있는 타입이다.
비슷해보이지만, 두 타입은 차이가 있다.
any는 어떤 타입이던 상관 없음을, unknown은 어떤 타입인지 모름을 의미한다는 개념만 가지고 코드를 살펴보자.
let anyVar: any = 10;
anyVar = "hi";
let unknownVar: unknown = 10;
unknownVar = "hi";
두 타입 모두 저런 식으로 타입을 왔다갔다 하면서 사용할 수 있다.
let anyVar: any = 10;
anyVar.toUpperCase();
anyVar.toFixed();
let unknownVar: unknown = 10;
unknownVar.toUpperCase(); // error!!
unknownVar.toFixed(); // error!!
any 타입은 어떤 타입이든 상관없기에, 타입 검사를 하지 않고, 따라서 어떤 메서드를 사용하던 간에 에러를 발생시키지 않는 모습이다.
unknown 타입은 어떤 타입인지 모르기에, 특정 메서드 사용을 허용하지 않는다.
let anyVar: any = 10;
let num : number = 20;
num = anyVar;
let unknownVar: unknown = 10;
let num : number = 20;
num = unknownVar; // error!!
위와 같은 맥락으로, unknown 에는 뭐가 들어있을지 모르기 때문에 다른 변수에 할당하는 것을 금지한다.
저렇게 아무 동작도 안된다면, unknown 타입은 어떻게 사용해야 할까?
나중에 글을 적겠지만, 타입 좁히기 를 통해 가능하다.
if(typeof unknownVar === "string") {
unknownVar.toUpperCase(); // not error
}
이렇게 unknown 타입의 변수가 string 타입임을 명시해줘야 비로소 string 타입의 메서드를 사용할 수 있게 되는 것이다.
아무것도 없음을 나타내기 위해 사용할 수 있는 타입으로 void와 never가 있다.
void는 단어 그 자체로, 존재하지 않음 공허 와 같은 의미를 지닌다.
따라서, 아무것도 없음을 나타낸다고 생각하면 된다.
function func1() : void {
console.log('아무것도 없어요!!'):
}
이렇게 어떠한 return 값도 반환하지 않는 함수에서 주로 사용한다.
기존에 배운 undefined 와 null을 아무것도 없음을 나타내는데 사용하면 될 것 같지만, 두 타입은 함수의 반환 타입으로 지정하는 순간, 실제로 undefined와 null을 반환해줘야 하기 때문에 문제가 된다.
물론, void 타입은 변수에 타입으로 지정할 수 있다.
let var1 : void;
var1 = 1; // error!!
var1 = "hi" // error!!
var1 = undefined // OK
var1 = null // error!! 하지만, strictNullChecks": false 라면 OK
하지만, undefined 를 제외하고는 어떤 값을 할당해도 에러가 난다.
tsconfig.json에서 "compilerOptions 의 옵션인 "strictNullChecks": false 즉, 엄격한 null 검사 옵션을 꺼주게 되면, null 까지도 할당 가능하다.
never 타입은 불가능한 존재하지 않는 이라는 의미를 갖는 타입이다.
정상적으로 종료가 되지 않기에, 예외를 던지거나, 무한 루프를 돌아서 종료에 도달할 수 없어 절대 반환이 불가능한 상황에 사용한다.
function func2(): never {
while(true) {}
}
funciton func3(): never {
throw new Error();
}
물론, never 타입도 변수의 타입으로 지정할 수 있다.
let neverVar: never;
neverVar = 1; // error!!
neverVar = 'hi'; // error!!
neverVar = undefined; // error!!
neverVar = null; // error!! strictNullChecks": false 여도 error.
그 어떤 값도 never 타입의 변수에 저장할 수 없다.
기존에 void 타입 변수에는 strictNullChecks": false 옵션을 꺼주면 null 을 할당할 수 있었는데, never 타입은 허용하지 않는다.
그렇다면, 두 변수를 console.log()로 찍게되면 어떻게 될까? 라는 궁금증이 들어서 해보게 되었다.
기존 JS에서는 변수를 선언만 한다면, undefined 가 할당되고, 출력해도 동일하다.
따라서 undefined를 할당할 수 있는 void 타입은 undefined 가 출력될 것으로 예상하지만, never 타입은 어떻게 나올지가 궁금했다.
let voidVar : void;
console.log(voidVar); // undefined
내 예상대로,void 타입은 undefined 를 출력한다.
let neverVar : void;
console.log(neverVar); // error. "변수가 할당되기 전에 사용되었습니다."
never 타입의 변수를 출력할 때는, 변수가 할당되기 전에 사용되었습니다. 라는 에러 문구를 띄워준다!!