Considere la siguiente declaración y usos de un tipo condicional:
type IsIterable<T> = T extends Iterable<infer X> ? Iterable<X> : never export function isIterableAny(val: any): val is IsIterable<typeof val> { return hasValue(val) && typeof (val as any)[Symbol.iterator] === "function" } export function isIterableUnknown(val: unknown): val is IsIterable<typeof val> { return hasValue(val) && typeof (val as any)[Symbol.iterator] === "function" } const num = 1 // 1 const arr = [1, 2, 3] // number[] type TestNumber = IsIterable<typeof num> // never type TestArray = IsIterable<typeof arr> // Iterable<number> if (isIterableAny(num)) { num /* 1 & Iterable<unknown> */ } if (isIterableUnknown(num)) { num /* never */ } if (isIterableAny(arr)) { arr /* number[] */ } if (isIterableUnknown(arr)) { arr /* never */ } ¿Por qué los resultados de la protección de tipo isIterable no se corresponden con los resultados de los tipos de prueba, aunque todos usan el mismo tipo condicional IsIterable ?
Me doy cuenta de que las dos versiones de type guard funcionan y fallan de formas diferentes/complementarias, pero, por supuesto, necesito un solo type guard que funcione para todas las entradas.
type IsIterable<T> = T extends Iterable<infer X> ? Iterable<X> : never;
Al usar esto, está descartando toda la información de tipo relacionada con T que no sean sus atributos iterables. En su lugar, solo necesita excluir de la unión T a todos los miembros que no extiendan el tipo Iterable :
export function isIterable<T>(value: T): value is T extends Iterable<any> ? T : never { try { return typeof (value as any)[Symbol.iterator] === 'function'; } catch { return false; } } declare const num: 1; isIterable(num) && num; // never declare const arr: number[]; isIterable(arr) && arr; // number[] declare const union: null | string[] | Generator<bigint> | Iterator<number> | Record<'a' | 'b', boolean>; isIterable(union) && union; // string[] | Generator<bigint>