Never
never는 값이 결코 발생하지 않는 것을 나타내는 타입이다. 이는 함수나 메서드의 반환 타입으로 사용되거나, 변수나 매개변수의 타입으로 사용될 수 있다.
보통 never는 예외를 던지거나 무한 루프 등과 같이 실행이 끝나지 않는 경우에 사용된다.
function throwError(message: string): never {
throw new Error(message);
}
위의 throwError 함수는 항상 예외를 던지므로, 반환 타입으로 never를 사용한다. 이렇게 never를 사용하면, 해당 함수를 호출한 코드에서는 반환값을 받지 않아도 되기 때문에, 예외를 던지는 것과 같은 예기치 않은 동작을 방지할 수 있다.
또한 never는 다른 타입의 서브 타입이 아니기 때문에, 모든 타입에 할당될 수 있지만, 반대로 never는 다른 타입에 할당될 수 없다. 이는 never는 결코 발생하지 않는 값을 나타내기 때문이다.
let a: never;
let b: string = "hello";
a = throwError("error occurred"); // never 타입의 값을 할당
b = a; // 오류! never는 string의 서브타입이 아님
never 타입은 주로 함수의 반환 타입으로 사용된다. 이는 함수가 항상 예외를 던지거나 무한 루프 등의 실행이 끝나지 않는 경우에 해당한다. 예를 들어 다음과 같은 fail 함수가 있다고 가정해보자.
function fail(): never {
while (true) {
// 무한 루프
}
}
이 함수는 항상 무한 루프에 빠지므로, 반환 타입으로 never를 사용한다.
또한 never는 void와는 다르다.
void는 반환값이 없는 함수의 반환 타입으로 사용되지만, never는 값을 반환하지 않는 함수의 반환 타입으로 사용된다.
타입 가드(Type Guard)에서 사용하기
function isString(value: unknown): value is string {
return typeof value === "string";
}
이 함수는 매개변수로 받은 value가 string 타입인지 검사하며, value is string 구문을 통해 타입 가드를 사용한다.
그런데 만약 이 함수가 never를 반환하도록 수정하면, 다음과 같이 타입 가드를 더욱 강력하게 사용할 수 있다.
function isString(value: unknown): value is string {
if (typeof value === "string") {
return true;
}
throw new Error(`Expected string but got ${typeof value}.`);
}
이 함수는 매개변수로 받은 value가 string 타입이 아닐 경우, never를 반환하며 예외를 던지도록 수정되었다.
이렇게 never를 사용하면, 함수의 반환 타입이 false가 아니라 never가 되기 때문에, 타입 가드의 사용 범위가 더욱 확대된다.
제네릭 타입(Generic Type)에서 사용하기
function getArray<T>(items: T[]): T[] {
return items;
}
이 함수는 제네릭 타입을 사용하여 T 타입의 배열을 받아서 T 타입의 배열을 반환한다.
그런데 만약 never를 사용한다면, 다음과 같이 빈 배열을 반환하도록 함수를 수정할 수 있다.
function getArray<T>(items: T[]): T[] | never {
if (items.length === 0) {
throw new Error("Array must not be empty.");
}
return items;
}
이렇게 never를 사용하면, items 배열이 빈 배열인 경우, never를 반환하도록 한다.
이는 반환 타입으로 T[] 대신 T[] | never를 사용하므로써, 빈 배열이 들어올 경우 타입 에러를 발생시키게 된다. 이는 제네릭 타입을 사용할 때, 좀 더 엄격한 타입 검사를 위해서 never를 사용하는 경우가 많다.
never 타입은 주로 예외 처리나 무한 루프 등의 상황에서 사용되며, 함수의 반환 타입이나 제네릭 타입에서 유용하게 사용된다. 이를 통해 타입스크립트에서 좀 더 엄격한 타입 검사를 수행할 수 있다.
Any
any는 모든 타입을 나타내는 특별한 타입으로, 해당 변수나 매개변수가 어떤 타입인지 정확히 모를 때 사용된다.
any를 사용하면 TypeScript의 타입 검사를 우회하고 JavaScript와 같이 동작하게 된다.
any는 유용한 경우가 있지만, 너무 많이 사용하면 타입 안정성을 잃을 수 있다. any를 사용하면 해당 변수의 값이 어떤 타입인지 명확하지 않아도 되기 때문에, 해당 변수를 사용할 때 적절한 타입 검사나 타입 변환을 수행하지 않으면 에러가 발생할 가능성이 높다.
let example: any;
example = 42; // 유효한 값이다.
example = "Hello"; // 유효한 값이다.
example = true; // 유효한 값이다.
위 코드에서 example 변수는 any 타입으로 선언되어 있기 때문에, 숫자, 문자열, 불리언 등 어떤 값이라도 할당할 수 있다.
any는 TypeScript의 타입 시스템의 강점을 활용하지 못하는 코드를 작성할 수 있으므로, 사용은 가능한한 자제해야 한다. 대신에, TypeScript에서 제공하는 다양한 타입 중에서 가장 적합한 타입을 선택하여 사용하는 것이 좋다.
any는 JavaScript와의 상호 운용성을 위해 도입되었다. JavaScript의 변수는 언제든지 어떤 값이든 할당할 수 있기 때문에, TypeScript에서도 이와 같은 유연성을 제공하기 위해 any 타입을 도입한 것이다.
let value: any = 42;
console.log(value.toFixed(2));
위 코드에서 value 변수는 any 타입으로 선언되어 있기 때문에, 숫자 값을 할당할 수 있다. 그리고 toFixed 메서드를 호출하여 소수점 이하 두 자리까지 반올림한 값을 출력하고 있다. 만약 value 변수가 숫자가 아닌 다른 타입의 값이었다면, 런타임 에러가 발생했을 것이다. 하지만 any 타입을 사용하면, TypeScript의 타입 검사를 우회하여 위와 같은 코드를 작성할 수 있다.
any를 남발하는 것은 좋지 않다. any를 사용하면 해당 변수나 매개변수의 타입 안정성이 보장되지 않기 때문에, 코드의 예측 가능성이 떨어질 수 있다. 따라서 가능한 any 타입을 사용하는 것은 피하고, TypeScript에서 제공하는 다양한 타입 중에서 가장 적합한 타입을 선택하여 사용하는 것이 좋다.
Unknown
unknown은 TypeScript 3.0에서 추가되었으며 모든 값의 타입을 표현하는 최상위 타입 중 하나다. unknown 타입은 any와 비슷하지만, any보다 더 제한적이며 안전한 타입이다.
unknown 타입은 다음과 같은 특징을 가지고 있다.
- unknown 타입은 모든 타입의 상위 타입이다. 즉, unknown 타입 변수는 어떤 타입의 값이든 할당할 수 있다.
- unknown 타입 변수를 사용할 때는 반드시 타입 체크나 타입 캐스팅(casting) 등을 통해 타입을 명시적으로 지정해주어야 한다. 그렇지 않으면 컴파일 에러가 발생한다.
- unknown 타입 변수는 타입 가드를 사용하여 타입을 추론할 수 있다. 타입 가드를 사용하면 unknown 타입 변수를 다른 타입 변수로 캐스팅할 수 있다.
- unknown 타입 변수는 대부분의 연산이 불가능한다. 예를 들어, unknown 타입 변수와 number 타입 변수를 더하면 컴파일 에러가 발생한다.
unknown 타입은 보통 런타임에 동적으로 생성되는 값이나 다른 라이브러리의 API를 사용할 때 유용한다. 이러한 경우 타입이 미리 알려지지 않기 때문에 unknown 타입을 사용하여 안전하게 처리할 수 있다.
function add(a: unknown, b: unknown): unknown {
if (typeof a === "number" && typeof b === "number") {
return a + b;
} else {
return NaN;
}
}
이 함수는 unknown 타입으로 선언된 두 개의 변수 a와 b를 입력으로 받아 두 변수가 모두 숫자일 경우에만 두 값을 더해 반환한다. 만약 a와 b 중 하나라도 숫자가 아니라면 NaN을 반환한다. 이러한 동작은 unknown 타입이 제공하는 안정성과 유연성을 모두 활용하고 있다.
unknown 타입 변수를 사용할 때는 타입 가드를 사용하여 타입을 추론할 수 있다.
function processInput(input: unknown): void {
if (typeof input === "string") {
console.log(input.toUpperCase());
} else if (Array.isArray(input)) {
console.log(input.join(", "));
} else {
console.log("Unknown input");
}
}
이 함수는 unknown 타입으로 선언된 input 변수를 입력으로 받아 해당 변수가 문자열인 경우 대문자로 변환하여 출력하고, 배열인 경우 배열 요소를 쉼표로 구분한 문자열을 출력한다. 만약 input 변수가 문자열이나 배열이 아니라면 "Unknown input"을 출력한다.
이처럼 unknown 타입은 타입 안전성을 높이기 위해 사용되며, 타입을 확실히 알 수 없는 경우에 유용하다. 다른 타입과 호환되기 때문에, 코드의 유연성을 높이는 데에도 도움이 된다.