만약 똑같은 타입을 가지고 여러 개의 값을 만들어야 한다면, 동일한 코드(타입에 대한 명시)가 무진장 많아질 것이다. 이런 부분을 위해서 나온 것이 타입 별칭이다.
똑같은 타입을 여러개 사용한다면 마치 변수처럼 타입을 지정해서 사용 가능하다.
// 타입 별칭
// 똑같은 타입을 여러개 사용한다면 타입을 마치 변수처럼 지정
type User = {
id: number;
name: string;
nickname: string;
location: string;
};
const user1: User = {
id: 1,
name: "su",
nickname: "su",
location: "seoul",
};
const user2: User = {
id: 2,
name: "ji",
nickname: "ji",
location: "Incheon",
};
주의할 점은, 변수처럼 같은 스코프 내에서 똑같은 이름의 타입 별칭 사용 불가하다는 점이다.
타입 별칭을 사용하면서 만약 똑같은 타입 명시가 반복되어야 한다면 인덱스 시그니쳐를 이용해서 코드를 줄일 수 있다.
만약 속성 값이 계속해서 키는 문자열로 들어가고 값은 숫자로 들어간다면 아래와 같은 코드로 인덱스 시그니쳐를 설정하면 된다.
// 인덱스 시그니쳐
type CityCodes = {
[key: string]: number;
seoul: number;
};
let cityCodes: CityCodes = {
seoul: 1,
incheon: 2,
bucheon: 3,
busan: 4,
};
인덱스 시그니쳐를 사용함과 동시에, seoul: number와 같이 내가 추가적인 프로퍼티를 작성하고 그 타입을 명시해주었다.
이때 주의할 부분은, 추가적인 프로퍼티의 타입과 인덱스 시그니쳐 프로퍼티의 타입이 일치하거나 호환되어야한다!.
만약 seoul : string이면 에러가 나온다.
enum 타입은
여러가지 값들에 각각 이름을 부여해 열거해두고 사용하는 타입을 말한다.
// enum 타입
enum Role {
ADMIN,
USER,
GEUST,
}
enum Language {
korean = "ko",
english = "en",
}
const user1 = {
name: "suji",
role: Role.ADMIN, // 0
lang: Language.korean, // "ko"
};
이때 숫자형 enum 타입의 경우 값을 직접 명시해주지 않아도 처음에 입력된 속성부터 값이 0으로 지정되어 1씩 늘어난다. 혹은 처음 속성의 값을 11로 지정하면 이후의 값이 1씩 늘어난다. 따라서 Role의 ADMIN은 값을 명시하지 않아도 0으로 지정되고, USER는 1, GEUST는 2가 지정된다.
문자형 enum은 위 예시처럼 내가 지정한 값으로 사용하게 된다.
참고로 ts의 enum은 컴파일 결과 사라지지 않고 js의 객체 형태로 변환되어 살아있다. 그래서 Role.ADMIN으로도 사용가능한 것...
// any
// 특정 변수의 타입을 우리가 확실히 모를 때 사용
// 아무 연산이나 메서드나 아무 변수에 값을 넣는 것이 가능
// 타입 에러를 런타임에 던진기 때문에 타입스크립트의 이점을 포기하는 것과 마찬가지
let anyVar: any = 12;
anyVar = "hi";
anyVar.toUpperCase(); // 메서드 사용 가능
let num: number = 1;
num = anyVar; // "hi"
// unknown
// 아무 연산이나 메서드나 아무 변수에 값을 넣는 것이 불가능
let unknowVar: unknown = 1;
unknowVar = "hello";
unknowVar.toUpperCase(); // 에러 메서드 사용 불가능
num = unknowVar; // 'unknown' 형식은 'number' 형식에 할당할 수 없습니다. 에러
void는 아무것도 없음을 나타내는 타입이다.
예를 들어 함수에서 반환값이 존재하지 않을 때 void 타입을 명시하는 것이 적절하다. 아래 예시 코드를 보면
// void 타입
// 아무것도 없음을 나타내는 타입
function func1(): void {
console.log("void는 이런거란다.");
}
const a: void = undefined; // 가능
const b: void = null; // nullCheck false면 가능/ 아니면 에러
func1에서 볼 수 있듯이 반환값이 없을때, void를 사용한다.
여기서 undefined로 타입 명시하면 되지 않느냐?라고 생각할 수도 있다.
하지만 undefined로 하고 실제 return 값을 안주면 에러가 발생한다.
function func2(): undefined {
console.log("hi");
// error : 선언된 형식이 'void'도 'any'도 아닌 함수는 값을 반환해야 합니다.
}
never는 불가능하거나 절대 값이 존재할 수 없을때 사용하는 타입이다.
void랑 느낌이 다른거다. 그러니까, 무한루프일때는 return 값이 나올 수가 없어서 이 함수가 종료되는 것이 불가능하므로 void가 아닌 never를 명시해주는 것이다. 혹은 new Error() 함수로 함수 실행중에 에러를 던지는 것 또한 함수를 정상적으로 종료하는 것이 불가능하므로 never가 적합하다.
function func3(): never {
while (true) {}
}
function func4(): never {
throw new Error();
}
const a: never = undefined; // 불가능
const b: never = null; // nullCheck false여도 불가능
이때 void와 또 다른 점이 하나 있다.
void로 타입을 지정한 변수는 undefined 값을 넣어줘도 됐지만
never 타입을 지정한 변수는 undefined 값을 넣을 수 없다.
또한 nullCheck가 false여도 null 값을 넣을 수 없다.
any는 타입스크립트의 이점을 포기하는 것과 마찬가지이므로 되도록 사용하지 말고 타입을 알지 못할 경우라면, unknown을 사용하는 것이 적절할 것 같다. unknown을 사용했을때 타입을 좁히면 그 타입에 따른 메서드를 사용할 수 있게 되니까 이 방법을 사용하면 될듯.
void랑 never는 처음에는 좀 감이 안왔는데 while문과 에러 던지는 함수를 보고 온전하게 함수가 종료될 수 없는 것을 기점으로 구분하니까 이해가 간다.