我有一个简单的函数,大体上工作很简单,但几乎每次我操作数据时,它的类型描述都很糟糕,需要类型断言。
函数:
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'
}
};我不知道接下来还能去哪里。
根据suggestion of Ingo Bürk (如下),我尝试了重载,他们解决了原始问题,但提出了一个新的问题,让我困惑:
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;
}
}如果我将当前(泛型)定义添加为缺省定义,上述错误就会消失,但再次需要类型断言。
发布于 2018-12-04 16:11:33
下面是如何使用重载来完成此操作。注意,实际的函数体是无类型的,我找不到一个好的方法来让它工作(甚至不确定它是否可能)。但是函数调用是正确键入的。
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}`); // stringhttps://stackoverflow.com/questions/53578459
复制相似问题