기술 블로그

Type Guard와 every 메서드의 한계 본문

카테고리 없음

Type Guard와 every 메서드의 한계

jaegwan 2024. 10. 14. 11:45
반응형

문제 상황: Type Guard와 every 메서드의 한계

TypeScript는 코드의 타입 안전성을 보장하기 위해 컴파일 시점에 타입을 검사한다. 이 과정에서 타입 가드타입 단언을 활용해 컴파일러가 타입을 정확하게 추론하도록 돕는다. 하지만 때로는 타입 가드를 사용했음에도 TypeScript가 배열의 타입을 확신하지 못하는 경우가 생긴다.

예시 코드

다음 예제에서 result 배열이 FileSystemFileEntry 또는 FileSystemDirectoryEntry로만 구성되어 있는지를 every 메서드를 통해 확인하고자 한다.

if (
    result.length > 0 &&
    result.every(entry => isFileSystemFileEntry(entry) || isFileSystemDirectoryEntry(entry))
) {
    resolve(result); // 오류 발생: TypeScript는 여전히 result의 타입을 알 수 없음
}

여기서 TypeScript는 result(FileSystemFileEntry | FileSystemDirectoryEntry)[] 타입임을 보장하지 않는다. 비록 every로 각 요소의 타입을 확인했지만, 컴파일러는 여전히 result 배열 전체가 안전한 타입인지 확신하지 못한다.

왜 발생하는가?

every 메서드는 배열의 모든 요소가 조건을 만족하는지 여부만을 반환한다. 즉, true 또는 false 값을 반환할 뿐이며, 이 정보만으로는 컴파일러가 result 전체 배열의 타입을 자동으로 강제할 수 없다.

타입 추론의 한계

타입 가드가 특정 요소의 타입을 좁히는 데는 효과적이지만, every와 같은 메서드는 배열 자체의 타입을 좁히지 않는다. TypeScript는 every의 결과가 true일 때, 개별 요소들이 특정 타입임을 알 수 있을지라도 배열 전체의 타입은 추론하지 않는다. 예를 들어, resultFileSystemEntry[] 타입으로 선언되어 있다면 every 이후에도 여전히 FileSystemEntry[] 타입으로 인식하게 된다.

TypeScript 컴파일러가 불완전하다고 느낄 수도 있지만, 사실 TypeScript는 의도적으로 배열의 전체 타입을 자동으로 추론하지 않도록 설계된 것이다. 그 이유는 TypeScript의 타입 시스템이 정적 타입 검사를 수행하기 때문이다. 즉, 컴파일 시점에만 타입을 검사하며, 런타임에서의 변화는 감지하지 않는다.

왜 TypeScript는 every의 결과로 타입을 강제하지 않는가?
every 메서드는 배열의 모든 요소가 특정 조건을 만족하는지를 검사하지만, 이때 반환되는 값은 오직 true나 false이다. 이렇듯 every는 단순히 배열이 조건을 만족하는지 아닌지를 알려주는 용도로 사용된다. TypeScript는 런타임에서의 조건을 기반으로 배열의 전체 타입을 좁히는 작업을 하지 않는다.

예를 들어, TypeScript가 result.every(...)의 결과가 true라고 해서, result의 타입을 자동으로 (FileSystemFileEntry | FileSystemDirectoryEntry)[]로 확신한다고 가정해보자. 만약 배열이 변경되거나, 이후 코드에서 다른 타입의 요소가 추가된다면, TypeScript는 컴파일 시점에서 이 변경을 미리 감지할 수 없게 된다. 따라서 컴파일 시점의 타입 검사에서는 every의 검사 결과로 배열 전체 타입을 확신하지 않도록 하고, 대신 개발자가 타입을 직접 좁히거나 명시적으로 지정하도록 하는 것이다.

해결 방법

every를 사용할 때 TypeScript가 배열의 타입을 확신하게 하려면, 타입 가드를 통해 배열을 다시 선언해줄 필요가 있다. 이를 해결하는 방법은 크게 두 가지로 나뉜다.

1. 타입 단언 사용

타입 단언을 사용하여 TypeScript에게 result가 안전한 타입임을 명시할 수 있다:

resolve(result as (FileSystemFileEntry | FileSystemDirectoryEntry)[]);

2. filter와 타입 가드 조합 사용

filter와 타입 가드를 조합하여 컴파일러가 배열의 타입을 안전하게 추론하도록 만들 수 있다. 이를 통해 타입 단언을 피하면서도 원하는 타입으로 제한할 수 있다.

const validEntries = result.filter(
    (entry): entry is FileSystemFileEntry | FileSystemDirectoryEntry =>
        isFileSystemFileEntry(entry) || isFileSystemDirectoryEntry(entry)
);

resolve(validEntries); // TypeScript는 validEntries가 (FileSystemFileEntry | FileSystemDirectoryEntry)[] 타입임을 확신한다.

여기서 filter 메서드는 새로운 배열을 반환하며, 이 배열이 FileSystemFileEntryFileSystemDirectoryEntry만 포함하고 있다고 TypeScript가 확신할 수 있도록 돕는다. 따라서 타입 단언 없이도 배열의 타입을 안전하게 추론할 수 있다.

결론

TypeScript는 개별 요소에 대한 타입 가드가 사용되더라도 배열 전체 타입을 자동으로 강제하지는 않는다. 이런 경우 filter와 타입 가드를 조합하여 컴파일러가 타입을 추론하도록 돕거나, 타입 단언을 통해 안전성을 명시해줄 수 있다. 이러한 이해를 통해 TypeScript의 타입 시스템을 더욱 효과적으로 활용할 수 있을 것이다.

반응형
Comments