본 내용은 한 입 크기로 잘라먹는 타입스크립트 강의를 바탕으로 작성되었습니다. 사진 자료의 출처 또한 위 강의입니다.
열거형 타입도 튜플 타입처럼 JS에는 없지만 타입스크립트에서는 사용할 수 있는 타입이다. 열거형은 여러개의 값을 나열하는 용도로 사용한다.
Enum을 사용하는 예시는 다음과 같다.
enum Role {
ADMIN,
USER,
GUEST,
}
위와 같이 3개의 멤버를 나열한 Role이라는 열거형 타입을 만들 수 있다. 숫자를 따로 할당해 주지 않으면 첫 멤버를 0으로 하고 1씩을 더해주며 자동으로 숫자를 할당해 준다.(마치 배열의 index와 같이...)
물론 숫자를 직접 할당해 줄 수도 있다. 시작하는 위치에 값을 직접 할당해 주면 자동적으로 아래 멤버들에도 1씩 증가된 값이 할당된다. 멤버의 값이 모두 숫자인 enum을 숫자 열거형 타입이라고 부른다는 것을 알아 두자.
enum Role {
ADMIN = 0,
USER = 1,
GUEST = 2,
}
const user1 = {
name: "홍길동",
role: Role.ADMIN, // 0
}
Enum은 위 예시의 유저 권한과 같이 여러개의 멤버를 갖는 값을 숫자로 표기할 때 사용하여 안전하고 직관적인 값 관리를 할 수 있다.
Enum의 멤버에 문자열 값을 할당해 주면 문자열 열거형 타입이 된다.
enum Language {
korean = "ko",
english = "en",
}
숫자 열거형 타입처럼 프로퍼티 값으로 활용할 수 있다.
enum은 다른 타입들처럼 컴파일 결과 사라지는게 아니라 JS 객체로 변환이 된다. 그래서 객체의 프로퍼티에 접근하는 것처럼 값으로 사용할 수 있었던 것이다.
any 타입은 타입 검사를 받지 않는 특수한 타입이다. 타입스크립트는 변수의 타입을 정의된 타입 또는 따로 타입 정의가 없을 때는 초기화 하는 값을 기준으로 추론한다.
예를 들어 다음과 같은 코드가 있으면
let anyVar = 10;
anyVar = "hello"; // Error
변수 anyVar
는 초기화 시점에 number 타입으로 추론된다. 따라서 문자열 값을 할당하려고 하면 타입 오류가 나는 것이다. 이럴 때 사용할 수 있는 것이 any 타입이다.
let anyVar: any = 10;
let num: number = 10;
anyVar = "hello";
anyVar = true;
anyVar = {};
num = anyVar; // number 타입 변수에 any 타입 변수 할당 가능
anyVar.toUpperCase();
anyVar.toFixed();
anyVar.a;
변수의 타입을 any로 정해주면 타입과 상관없이 모든 값을 담아 사용할 수 있게 된다. 따라서 타입과 상관없이 어떤 메서드든 호출하여 사용할 수 있다. 또 any 타입의 값은 어떤 타입의 변수이든 상관없이 모두에게 할당해 줄 수 있다.
다만, 이렇게 유연함을 주는 타입이기 때문에 문제점이 발생한다. 위 코드를 실행해 보면 런타임 에러가 발생한다. 마지막으로 anyVar
에 객체를 할당해 주었는데 toUpperCase()
메서드는 객체에 사용이 불가능하기 때문이다.
이렇게 타입 검사가 제대로 이루어지지 않아 위험한 코드가 생산될 수 있고, 타입스크립트를 사용하는 장점이 사라진다. 따라서, 꼭 필요한 경우가 아닌 이상 any 타입은 사용하지 않는 것이 권장된다.
unknown 타입의 변수는 어떤 타입의 값이든 모두 저장할 수 있다는 면에서 any 타입과 비슷하다.
let unknownVar: unknown;
unknownVar = 10;
unknownVar = "hello";
unknownVar = true;
하지만 unknown 타입의 값을 어떤 타입의 변수에도 저장할 수 없다는 점,
let num: number;
let unknownVar: unknown = 10;
num = unknownVar; // Error
어떤 연산도, 메서드도 사용할 수 없다는 점에서 any 타입과 차이가 있다.
let unknownVar: unknown = 10;
unknownVar * 10; // Error
unknownVar.toFixed(); // Error
즉, 어떤 타입의 값이든 할당받을 수 있는 것은 같지만, 더 안전한 사용이 가능한 타입인 것이다. 오직 값을 저장하는 행위밖에 못하는 타입이라 할 수 있다.
물론, unknown 타입의 변수로 연산이 가능하도록 할 수는 있다. '타입 좁히기'로 변수의 타입을 보장해 주면 된다.
타입스크립트에서 조건물을 이용해 특정 값이 특정 타입임을 보장해 준다면 자동으로 타입이 변경되기 때문이다.
if (typeof unknownVar === "number") {
unknownVar * 10;
}
결론적으로, 더 안전하기 때문에 변수가 어떤 타입의 값을 받게 될지 모른다면 any 타입보다는 unknown 타입을 선택하는 것이 낫다.
void 타입은 아무런 값도 없음을 의미하는 타입이다. 일반적으로 반환값이 없는 함수의 반환값 타입을 정의할 때 사용한다.
function sayHello(): void {
console.log("Hello");
}
변수의 타입으로도 void 타입을 지정할 수 있다. 변수 타입이 void라면 그 값으로 undefined
또는 null
밖에 갖지 못한다. 심지어 strictNullChecks 옵션이 켜져 있다면 null
값도 갖지 못한다.
let var: void;
var = undefined;
이미 JS에는 아무 값도 없음을 의미하는 undefined와 null이 존재한다. 그런데 굳이 void 타입이 필요한 이유는 무엇일까?
앞서 살펴본 반환값이 없는 함수의 반환값 타입을 각각 undefined와 null로 변경해 보자.
function sayHello1(): undefined {
console.log("Hello");
return undefined; // undefined 값 자체를 반환해야 함
}
function sayHello2(): null {
console.log("Hello");
return null; // null 값 자체를 반환해야 함
}
위와 같이 반환값 타입을 undefined나 null로 정의하게 되면 실제로 그 값으로 반환을 해주어야 한다. return문 자체가 없는 함수의 반환값 타입을 정의해 줄 수 없는 것이다. 이럴 때 void 타입을 사용하게 되고, 필요한 것이다.
never 타입은 불가능을 의미하는 타입이다. 일반적으로 함수가 어떤 값도 반환할 수 없는 상황일 때 함수의 반환 값 타입으로 정의해준다.
function ex1(): never {
while (true) {} // 무한루프
}
function ex2(): never {
throw new Erorr(); // 항상 오류가 발생
}
함수의 반환값을 정의하는 것을 살펴보았고, 변수의 타입을 never로 정의해 준다면 any를 포함한 그 어떤 타입의 값도 변수에 할당할 수 없게 된다.