У меня есть простая функция, которая в принципе выполняет простую работу, но плохое описание типа, требующее подтверждения типов почти каждый раз, когда я манипулирую данными.
Функция:
const fetch_and_encode = <T, E extends Encoded, C>({ source, encode, context }: {
source: Fetcher<T | E> | T | E,
encode?: Encoder<T, E, C>,
context?: C
}): E => {
let decoded;
if ( typeof source === 'function' ) {
decoded = (source as Fetcher<T | E>)();
} else {
decoded = source;
}
if ( typeof encode === 'function' ) {
return encode(decoded as T, context);
} else {
return decoded as E;
}
};
Указанные типы:
export type Encoded = number | string | ArrayBuffer // | Array<Encoded> | Map<string, Encoded>
export type Fetcher<T> = () => T;
export type Encoder<T, E extends Encoded, C> = (decoded: T, context?: C) => E;
В основном он имеет 2 переменные source
и encode
, каждая из которых может иметь один из двух действующих типов, что приводит к 4 состояниям.source
- это либо элемент данных, либо функция, которая извлекает элемент данных.encode
- это либо undefined
, либо функция, которая преобразует «результат» source
.В конце концов, эта комбинация должна привести к значению (относительно) простого типа, Encoded
.
Я пытался улучшить определение типа несколькими способами, но, похоже, не могу избежать необходимостиутверждения типа.Эти попытки также были направлены как на углубление моего понимания системы типов, так и на очистку фактических определений.Тем не менее, мне кажется, что я должен быть способен задавать типы достаточно плотно, чтобы избежать утверждений типа, и я хотел бы понять, как.
Моя первая попытка с использованием союзов, похоже, нена самом деле вообще улучшить определение типа:
const fetch_and_encode = <T, E extends Encoded, C>( {source, encode, context}: {
source: Fetcher<T>;
encode: Encoder<T, E, C>;
context?: C;
} | {
source: Exclude<T, Function>; // T could still be a subtype of Function
encode: Encoder<T, E, C>;
context?: C;
} | {
source: Fetcher<E>;
encode: undefined;
context?: any;
} | {
source: E;
encode: undefined;
context?: any;
}): E => {
let decoded;
if ( typeof source === 'function' ) {
decoded = (source as Fetcher<T | E>)();
// decoded = source(); // Cannot invoke an expression whose type lacks a call signature. Type 'Fetcher<T> |
// // Fetcher<E> | (Exclude<T, Function> & Function)' has no compatible call signatures.
} else {
decoded = source;
}
if ( typeof encode === 'function' ) {
return encode(decoded as T, context);
// return encode(decoded, context); // Argument of type 'T | E' is not assignable to parameter of type 'T'
} else {
return decoded as E;
// return decoded; // Type 'T | E' is not assignable to type 'E'
}
};
Затем я попытался использовать фактические условные типы, но также не получил ничего:
const fetch_and_encode = <T, E extends Encoded, C>({ source, encode, context }: {
source: Fetcher<T | E> | T | E,
encode: Encoder<T, E, C> | undefined,
context: C | undefined
} extends { source: infer S, encode: infer N, context?: C }
? S extends Function // Ensure S isn't a Function if it also isn't a Fetcher
? S extends Fetcher<T | E>
? N extends undefined
? { source: Fetcher<E>; encode: undefined; context?: any; }
: { source: Fetcher<T>; encode: Encoder<T, E, C>; context?: C; }
: never
: N extends undefined
? { source: E; encode: undefined; context?: any; }
: { source: T; encode: Encoder<T, E, C>; context?: C; }
: never
): E => {
let decoded;
if ( typeof source === 'function' ) {
decoded = (source as Fetcher<T | E>)();
// decoded = source(); // Cannot invoke an expression whose type lacks a call signature. Type 'Fetcher<T> |
// // Fetcher<E> | (T & Function)' has no compatible call signatures.
} else {
decoded = source;
}
if ( typeof encode === 'function' ) {
return encode(decoded as T, context);
// return encode(decoded, context); // Argument of type 'T | E' is not assignable to parameter of type 'T'
} else {
return decoded as E;
// return decoded; // Type 'T | E' is not assignable to type 'E'
}
};
Я не уверен, куда еще идтиздесь.
Согласно предложению Инго Бюрка (ниже), я пробовал перегрузки, и они решили первоначальные проблемы, но представили новую, которая меня озадачивает:
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
// ^^^^^^^^^^^^^^^^ Overload signature is not compatible with function implementation
source: E;
encode: undefined;
context?: any;
}): E;
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
source: Fetcher<E>;
encode: undefined;
context?: any;
}): E;
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
source: Fetcher<T>;
encode: Encoder<T, E, C>;
context?: C;
}): E;
function fetch_and_encode<T, E extends Encoded, C>({ source, encode, context }: {
source: Exclude<T, Function>; // T could still be a subtype of Function
encode: Encoder<T, E, C>;
context?: C;
}): E {
let decoded;
if ( typeof source === 'function' ) {
decoded = source();
} else {
decoded = source;
}
if ( typeof encode === 'function' ) {
return encode(decoded, context);
} else {
return decoded;
}
}
Если я добавлю мое текущее (универсальное) определение в качестве значения по умолчанию, вышеуказанная ошибка исчезнет, но утверждения типа снова потребуются.