我有一个简单的函数,原则上来说,它的工作很简单,但是几乎每次我处理数据时,类型描述都很差,需要类型声明。
功能:
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;
它基本上有两个变量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'
}
};
我不确定从这里还能去哪里。
对于{{3}}(如下),我尝试了重载,它们解决了最初的问题,但是提出了一个使我感到困惑的新问题:
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;
}
}
如果我将当前(通用)定义作为默认值添加,则上述错误消失了,但是再次需要类型断言。
答案 0 :(得分:1)
这是超载时的处理方法。请注意,实际的函数主体是无类型的,我找不到使它正常工作的好方法(并且不确定是否有可能)。但是函数调用的类型正确。
function isFetcher<T>(obj: T | Fetcher<T>): obj is Fetcher<T> {
return typeof obj === "function";
}
function fetchAndEncode<A extends Encoded>(source: A | Fetcher<A>): A;
function fetchAndEncode<A, B extends Encoded, C>(source: A | Fetcher<A>, encode: Encoder<A, B, C>, context?: C): B;
function fetchAndEncode(source: any, encode?: any, context?: any) {
const datum = isFetcher(source) ? source() : source;
return encode ? encode(datum, context) : datum;
}
这将通过以下类型测试:
let numericValue: number;
fetchAndEncode(numericValue); // number
fetchAndEncode(true); // error
fetchAndEncode(numericValue, val => `Hello ${val}`); // string
fetchAndEncode(() => numericValue); // number
fetchAndEncode(() => true); // error
fetchAndEncode(() => numericValue, val => `Hello ${val}`); // string