any 타입은 타입스크립트의 타입 체킹 기능을 비활성화하기 때문에,
가능한 한 좁은 범위에서만 사용해야 합니다.
- 위의 내용은 이 본문 전체를 관통하는 이야기이다. 글쓴이는 any를 써야 한다면 최대한 좁게 만들라는 이야기를 계속해서 반복한다.
다음은 any 타입을 사용하는 두 가지 방법이다:
// 나쁜 예
function f1() {
const x: any = expressionReturningFoo(); // bad
processBar(x);
};
// 좋은 예
function f2() {
const x = expressionReturningFoo();
processBar(x as any); // good
};
| processBar 함수의 매개변수에서만 any 타입을 사용하는 것이 좋다. 이는 다른 코드에는 영향을 미치지 않기 때문.
// 나쁜 예
const config: Config = {
a: 1,
b: 2,
с: {
key: value
}
} as any; // bad
// 좋은 예
const config: Config = {
a: 1,
b: 2,
c: {
key: value as any // good
}
};
위 예시에서도 보듯, any 타입은 가능한 최소한의 범위에서만 사용하는 것이 바람직하다.
any를 구체적으로 변형해서 사용하기
any의 범위를 가능한 한 줄이기 위해, 더 구체적인 타입으로 변형해서 사용하는 것이 좋습니다.
예를 들어, 다음과 같은 방법을 사용할 수 있다.
// 나쁜 예
function getLengthBad(array: any) {
return array.length;
}
// 좋은 예
function getLength(array: any[]) {
return array.length;
}
| 함수 내의 array.length 타입이 체크되고, 함수의 반환 타입이 any 대신 number로 추론된다. 또한, 함수가 호출될 때 매개변수가 배열인지 체크된다.
배열의 배열 형태라면 any[][], 객체이지만 값을 알 수 없다면 {[key: string]: any} 처럼 선언할 수 있다.
함수 안으로 타입 단언문 감추기
함수를 만들 때, 내부 로직에 모두 안전한 타입을 붙이는 것은 어려운 작업이 될 수 있습니다. 이때, 함수의 타입 정의만 보면 간단해 보이지만, 실제 구현체에서는 any 또는 타입 단언문을 사용하는 것이 괜찮습니다.
예를 들어, 반환 값에 타입 단언문을 사용하는 경우:
function cacheLast<T extends Function>(fn: T): T {
// 생략...
return function(...args: any[]) {
// 생략...
return lastResult;
} as unknown as T;
}
함수 내부 연산에서 타입 단언문을 사용하는 경우:
function shallowObjectEqual<T extends object>(a: T, b: T): boolean {
for (const [k, aVal] of Object.entries(a)) {
if (!(k in b) || aVal !== (b as any)[k]) {
return false;
}
}
return Object.keys(a).length === Object.keys(b).length;
}
이처럼 타입 단언문을 사용하면, 타입스크립트의 타입 체크를 우회할 수 있으며, 코드의 복잡성을 줄일 수 있습니다.
any의 진화를 이해하기
any 타입은 다양한 상황에서 진화할 수 있습니다. 예를 들어, 변수에 할당된 값에 따라 any 타입의 범위가 좁아지는 경우를 살펴봅시다.
let val; // any
if (Math.random() < 0.5) {
val = /hello/;
val; // RegExp
} else {
val = 12;
val; // number
}
val; // number | RegExp
| 이러한 진화를 통해 any 타입을 더 구체적인 타입으로 좁혀나갈 수 있다. 하지만 명시적으로 any를 선언한 경우에는 진화가 일어나지 않는다.
모르는 타입의 값에는 any 대신 unknown을 사용하기
any 대신 unknown 타입을 사용하면, 타입에 대해 무엇인지 확실히 알기 전까지 그 값을 사용할 수 없게 됩니다. 이는 타입 안전성을 유지하면서 타입스크립트를 사용할 수 있게 해 줍니다.
예를 들어, 다음과 같이 사용할 수 있다:
function processValue(val: unknown) {
if (val instanceof Date) {
val; // 타입이 Date
}
}
| unknown 타입은 {}와 object 타입보다 더 넓은 범위의 값을 받을 수 있으며, 타입 안전성을 유지하면서 사용할 수 있다.
몽키 패치보다는 안전한 타입을 사용하기
자바스크립트에서는 window, document 등의 전역 변수에 값을 할당해 전역 변수를 임의로 수정할 수 있습니다. 이를 몽키 패치라고 합니다. 하지만 타입스크립트에서는 몽키 패치로 인해 타입 체크가 어려울 수 있습니다.
이를 해결하기 위해, 보강을 사용하거나 더 구체적인 타입 단언문을 사용할 수 있다:
1. 보강
interface Document {
/** 몽키 패치의 속(genus) 또는 종(species) */
monkey: string
}
document.monkey = 'Tamarin'; // 정상
2. 더 구체적인 타입 단언문 사용하기
interface MonkeyDocument extends Document {
/** 몽키 패치의 속(genus) 또는 종(species) */
monkey: string
}
(document as MonkeyDocument).monkey = 'Macaque';
이러한 방법을 통해 타입 안전성을 유지하면서 몽키 패치를 사용할 수 있습니다.
타입 커버리지를 추적하여 타입 안전성 유지하기
타입스크립트를 사용할 때, 타입 커버리지를 추적하는 것이 중요합니다. 타입 커버리지는 프로젝트의 코드 중 얼마나 많은 부분이 타입 체크에 의해 검사 되고 있는지를 나타냅니다. 타입 커버리지를 추적하기 위해 다음과 같은 명령어를 사용할 수 있습니다:
npx type-coverage
이 명령어를 사용하면, 타입 커버리지에 대한 정보를 얻을 수 있으며, 타입 안전성을 높이기 위해 어떤 부분을 개선해야 할지 파악할 수 있다.