ts에서 타입 체킹의 주요 장점 중 하나는 런타임 이전에 정적으로 타입을 체크하여 잠재적 오류를 사전에 방지하는 것이다. 이러한 이점을 최대한 활용하기 위해, ts에서는 any 타입의 사용을 가능한 한 줄이는 것이 좋다. 이 장에서는 any 타입을 어떻게 효과적으로 관리하고, 안정성을 유지하면서 코드의 유연성을 높일 수 있는지에 대해 알수 있다.
함수에서 any를 사용할 때는 가능한 한 좁은 범위에서 사용해야 한다. any를 반환하게 되면, 이는 외부 코드에 any가 퍼져 타입 체커의 이점을 활용할 수 없게 만든다.
type Foo = { name: string };
type Bar = { age: number };
declare function expressionReturnFoo(): Foo;
declare function processBar(b: Bar): void;
const f1 = () => {
const x: any = expressionReturnFoo(); // any 타입 지정
processBar(x); // 타입 체크 없이 사용 가능
return x; // any 반환 (위험)
};
위의 예에서 f1 함수는 any 타입을 반환하므로 외부에서 이 함수를 사용할 때 타입 체커가 제대로 동작하지 않는다. 이를 해결하기 위해 any 타입을 내부적으로만 사용하고, 외부로는 any를 반환하지 않는 방법을 사용해야 한다.
const f2 = () => {
x = expressionReturnFoo();
processBar(x as any); // 내부에서만 any 사용
return x; // Foo 타입 반환
};
객체에서 any를 사용할 때도 전체에 사용하기 보다 필요한 부분에만 사용하는 것이 좋다. 전체를 any로 지정하면, 객체의 모든 속성에 대한 타입 체크를 무시하게 된다.
type Foo ={ name: string };
type Config = {
a: number;
b: number;
c: { key: Foo };
};
const config: Config = {
a: 1,
b: 2,
c: {
key: 123 as any, // 부분적으로 any 사용
},
};
any의 사용 범위는 최소으로 좁히는 것이 좋다 함수에서는 반환 타입에 절대 any를 사용하지 않아야 하며, 객체에서는 전체 대신 필요한 부분에만 any를 사용해야 한다.이렇게 any 타입 사용을 최소화 하면 더 안정적인 코드를 작성할 수 있고, 런타임에서 발생할 수 있는류를 사전에 방지할 수 있다.
ts에서 any 타입을 사용할 때는 더욱 구체적인 형태로 사용하는 것이 중요하다. any 타입은 모든 타입을 허용하기 때문에, 가능한 한 구체적인 형태로 사용하여 코드의 안정성을 확보해야 한다.
any 타입을 사용할 때, 가능한 한 더 구체적인 형태로 사용하는 것이 좋다. 예를 들어 배열에서 길이를 구하는 함수를 작성할 때, any 대신 any[] 를 사용하면 배열에 대한 타입 체크가 가능해진다.
function getLength(array: any[]) {
return array.length;
}
위의 예시에서 getLength 함수는 any[] 타입을 매개변수로 받아, 함수 내의 array.length 타입을 체크하고, 반환 값의 타입을 number로 추론한다.
함수의 경우, 아래와 같이 any 대신 더 구체적인 형태를 사용할 수 있다.
type Fn0 = () => any; // 매개변수 없이 호출 가능한 모든 함수
type Fn1 = (arg: any) => any; // 매개변수 1개
type FnN = (...args: any[]) => any; // 모든 개수의 매개변수를 가진 함수
객체에서 any 타입을 사용할 때도, 가능한 한 더 구체적인 형태로 사용하는 것이 좋은데 예를 들어, 어떤 객체라도 받는 타입을 정할 때, object 나 인덱스 시그니처를 사용할 수 있다.
const hasTwelveLetterKey = (obj: { [key: string]: any }) => {
for (const key in obj) {
if (key.length === 12) return true;
}
return false;
};
위의 예시에서 hasTwelveLetterKey 함수는 인덱스 시그니처를 사용하여, 모든 키를 문자열로, 모든 값을 any 타입으로 가지는 객체를 매개변수로 받는다.
any 타입을 사용할 때는 필요한지 다시 한 번 확인하고, 최대한 구체적인 형태로 사용하여 코드의 안정성을 높여야 한다. ts에서는 타입 추론과 가독성을 위해 가능한 한 any를 지양하는 것이 좋다.
ts를 사용하다 보면, 함수 내부 로직이 복잡하게 되어 타입을 안전하게 구현하기 어려운 경우가 생길 수 있다. 이럴 때는 타입 단언문을 사용하여 타입을 강제하는 방법을 사용하게 되는데 하지만 이를 잘못 사용하면 코드의 안전성을 저해할 수 있기 때문에 주의가 필요하다.
타입 단언문은 일반적으로 타입을 위험하게 만들지만, 상황에 따라 필요하며 현실적인 해결책이 될 수 있다. 이를 안전하게 사용하기 위한 가장 좋은 방법은 필요한 경우에만 사용하고, 이를 함수 내부에 숨기는 것다.
아래의 예시는 객체가 같은지 비교하는 함수인데 이 함수에서는 b as any라는 타입 단언문을 사용하여 b[k] 가 가능하게 만들었다. 이는 k in b 체크를 통해 b 객체에 k 속성이 있다는 것을 확인했기 때문에 안전하다고 판단할 수 있다.
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 타입을 사용하여 어떠한 함수가 인자로 들어오더라도 처리할 수 있게 만들었다.
const cacheLast = <T extends Function>(fn: T): T => {
let lastArgs: any[] | null = null;
let lastResult: any;
return ((...args: any[]) => {
if (!lastArgs || !shallowEqual(lastArgs, args)) {
lastResult = fn(...args);
lastArgs = args;
}
return lastResult;
}) as unknown as T;
};
위의 예시에서 cacheLast 함수는 내부적으로 any 타입을 사용하지만, 외부로는 any 타입이 드러나지 않는다. 이렇게 함수 내부에서 any를 사용하되, 외부로는 안전한 타입을 드러내는 방법은 any 타입의 사용을 최소화하면서도 유연한 함수를 작성하는 데 도움이 될 수 있다.
함수 내부에서는 외부에 불필요한 정보를 노출시키지 않으면서도 타입 단언문을 활용하여 안전한 코드를 구현할 수 있다. ts를 사용하면서 타입 안정성을 유지하면서도 복잡한 내부 로직을 구현하는데 도움이 되는 타입 단언문을 적절히 활용할 수 있어야 한다.