打字稿'条件'类型
Posted
技术标签:
【中文标题】打字稿\'条件\'类型【英文标题】:Typescript 'Conditional' Types打字稿'条件'类型 【发布时间】:2019-05-03 20:15:49 【问题描述】:我有一个简单的函数,原则上它的工作很简单,但类型描述很差,几乎每次我操作数据时都需要类型断言。
功能:
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'
;
我不知道还能去哪里。
根据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;
如果我将当前(通用)定义添加为默认值,上述错误就会消失,但再次需要类型断言。
【问题讨论】:
重载签名可能是最简单的:typescriptlang.org/docs/handbook/functions.html#overloads 我还没有考虑过。我会试一试(并祈祷它不会太冗长;-) 源参数可能可以在没有重载的情况下处理,但对于编码情况,我认为它可能更容易。我只是在手机上,所以我现在不能自己玩。我相信提香很快就会过来,给你一个他惊人的答案。 过载...改变了问题。我会补充细节。 另一个想法是让编码器成为一个可选参数,默认为标识函数。这使得打字变得微不足道,尽管从技术上讲当然不能回答这个问题。 【参考方案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
【讨论】:
也许我的模型需要改进,但我明确需要该函数返回B
,因为这在其他地方受到限制(通过Encoded
)。 A
可能是未编码的类型,如果此函数的使用者提供了encode
。
@BenjaminRiggs 我没有关注,如果您通过了编码器,此解决方案会返回 B
?你能在电话打不通的地方发个电话吗?
如果encode
未定义,那么A
也必须扩展Encoded
,而且,根据我的阅读,这个例子并没有强制执行。
@BenjaminRiggs 我明白了,我完全错过了。我更新了我的答案。以上是关于打字稿'条件'类型的主要内容,如果未能解决你的问题,请参考以下文章