서론
이 글은 any-considered-harmful을 번역, 정리한 글임을 밝힙니다.
any를 사용하는 것은 대부분의 경우, 오용하기 쉬운 코드를 만들어 냅니다.
예를 들어 아래와 같은 myFunction은 런타임 에러를 발생시킵니다.
const myFunction = (input: any) => {
input.someMethod();
};
myFunction("abc"); // This will fail at runtime!
하지만 모든 케이스에서 any를 사용하면 안 되는 것은 아닙니다. 이번 글에서는 any의 사용이 유용한 경우에 대해서 알아보도록 하겠습니다.
매개변수 제약 조건
함수의 매개변수를 추론하는 ReturnType을 정의한다고 가정해 보겠습니다.
any를 사용하지 않는다면 args의 타입은 unknown을 사용할 수 있을 것 같습니다.
type ReturnType<T extends (...args: unknown[]) => unknown> =
// Not important for our explanation:
T extends (...args: unknown[]) => infer R ? R : never;
매개변수가 존재하지 않는 myFunction에서는 잘 동작합니다!
const myFunction = () => {
console.log("Hey!");
};
// void
type Result = ReturnType<typeof myFunction>;
하지만 매개변수가 존재하는 경우 아래와 같은 에러가 발생합니다.
const myFunction = (input: string) => {
console.log("Hey!");
};
// 🚫Type 'unknown' is not assignable to type 'string'.
type Result = ReturnType<typeof myFunction>;
이와 같은 상황에서으 any 타입 선언은 적절한 사용의 예가 될 수 있습니다.
any의 의미는 모든 타입을 수용할 수 있는 가장 넓은 타입이라는 의미입니다. 즉 무엇 어떤 타입이든 매개변수가 될 수 있다라는 상황에 알맞은 타입입니다.
type ReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
const myFunction = (input: string) => {
console.log("Hey!");
};
type Result = ReturnType<typeof myFunction>;
일반 함수에서 조건 타입 반환
아래와 같이 매개변수에 따라 hello, goodbye를 반환하는 함수가 있습니다.
그런데 반환 타입이 유니온 타입으로 나오기 때문에 반환 결과를 보장할 수 없는 문제가 남아있습니다.
const youSayGoodbyeISayHello = (
input: "hello" | "goodbye"
) => {
if (input === "goodbye") {
return "hello";
} else {
return "goodbye";
}
};
// result: "hello" | "goodbye"
const result = youSayGoodbyeISayHello("hello");
이 문제를 조건부 타입을 사용하면 개선할 수 있습니다.
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello" ? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello";
} else {
return "goodbye";
}
};
const goodbye = youSayGoodbyeISayHello("hello");
const goodbye: "goodbye"
const hello = youSayGoodbyeISayHello("goodbye");
const hello: "hello"
이제 문제가 해결됐을 까요? 안타갑게도 위 코드에는 사실 에러가 남아있습니다.
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello" ? "goodbye" : "hello" => {
if (input === "goodbye") {
// 🚫 Type '"hello"' is not assignable to type 'TInput extends "hello" ? "goodbye" : "hello"'.
return "hello";
} else {
// 🚫 Type '"goodbye"' is not assignable to type 'TInput extends "hello" ? "goodbye" : "hello"'.
return "goodbye";
}
};
타입스크립트가 아직 조건부 반환 타입과 로직을 일치시키지 못하여 발생하는 에러입니다. 이런 상황을 실무에서 발견했다면 아래와 같이 수정했을지 모릅니다.
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello" ? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello" as TInput extends "hello"
? "goodbye"
: "hello";
} else {
return "goodbye" as TInput extends "hello"
? "goodbye"
: "hello";
}
};
혹은 반복되는 조건부 타입이 거슬려서 아래처럼 개선했을지 모릅니다.
type YouSayGoodbyeISayHello<
TInput extends "hello" | "goodbye"
> = TInput extends "hello" ? "goodbye" : "hello";
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): YouSayGoodbyeISayHello<TInput> => {
if (input === "goodbye") {
return "hello" as YouSayGoodbyeISayHello<TInput>;
} else {
return "goodbye" as YouSayGoodbyeISayHello<TInput>;
}
};
하지만 이런 경우 아래처럼 any를 사용하는 것이 더 합리적입니다.
const youSayGoodbyeISayHello = <
TInput extends "hello" | "goodbye"
>(
input: TInput
): TInput extends "hello" ? "goodbye" : "hello" => {
if (input === "goodbye") {
return "hello" as any;
} else {
return "goodbye" as any;
}
};
물론 위 any를 사용함에 따라 덜 안전한 코드가 되긴 했습니다. 예를 들어 로직이 변경되면서 실제로는 "bonsoir"같은 반환값이 반환됨에도 타입스크립트는 "hello" 혹은 "goodbye"를 추론할 수 도 있습니다.
이런 문제에 대한 대안으로 as any를 사용하는 대신 단위 테스트를 추가하는 것이 나을 수 있습니다. 다시 말해 타입스크립트의 한계를 단위 테스트를 통해 보완하고 발생하는 타입 에러는 any로 처리하는 것입니다.
결론
몇 가지 any가 유용할 수 있는 사례에 대해 다뤄봤습니다. 하지만 일반적 상황에서 any를 제한하는 것이 유익한 경우가 훨씬 많습니다. 따라서 any 사용을 코드베이스에서 금지하고 예외적 상황에서만 eslint-disable를 통해 any를 제한적으로 사용하는 것이 낫다는 생각입니다.
참고
https://www.totaltypescript.com/any-considered-harmful
`any` Considered Harmful, Except For These Cases
Discover when it's appropriate to use TypeScript's `any` type despite its risks. Learn about legitimate cases where `any` is necessary.
www.totaltypescript.com
'typescript' 카테고리의 다른 글
타입스크립트의 공변성과 반변성 (2) | 2024.07.13 |
---|---|
Generic을 활용한 복잡한 구조의 타입 추론하기 (0) | 2024.07.05 |
실무자를 위한 현장 밀착 Typescript Best Practice (0) | 2024.06.21 |