我想创建一个行为与in
操作符完全相同的函数,其中使用用户定义的类型保护来缩小类型。
(For一个例子,参见Lodash的has
函数。)
对于x表达式中的n,其中n是字符串文字或字符串文字类型,x是联合类型,“true”分支缩小到具有可选或必需属性n的类型,“false”分支缩小到具有可选或缺少属性n的类型。
https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-the-in-operator
我写了一些测试,展示了in
操作符的行为以及has
函数的预期行为。如何定义一个函数(has
),使其行为与这些测试中的in
操作符完全相同?
declare const any: any;
type Record = { foo: string; fooOptional?: string };
type Union = { foo: string; fooOptional?: string } | { bar: number; barOptional?: string };
{
const record: Record = any;
if ('foo' in record) {
record; // $ExpectType Record
} else {
record; // $ExpectType never
}
if (has(record, 'foo')) {
record; // $ExpectType Record
} else {
record; // $ExpectType never
}
}
{
const union: Union = any;
if ('foo' in union) {
union; // $ExpectType { foo: string; fooOptional?: string | undefined; }
} else {
union; // $ExpectType { bar: number; barOptional?: string | undefined; }
}
if (has(union, 'foo')) {
union; // $ExpectType { foo: string; fooOptional?: string | undefined; }
} else {
union; // $ExpectType { bar: number; barOptional?: string | undefined; }
}
}
{
const unionWithOptional: { foo: string } | { bar?: number } = any;
if ('bar' in unionWithOptional) {
unionWithOptional; // $ExpectType { bar?: number | undefined; }
} else {
unionWithOptional; // $ExpectType { foo: string; } | { bar?: number | undefined; }
}
if (has(unionWithOptional, 'bar')) {
unionWithOptional; // $ExpectType { bar?: number | undefined; }
} else {
unionWithOptional; // $ExpectType { foo: string; } | { bar?: number | undefined; }
}
}
我最接近解决这个问题的方法是:
type Discriminate<U, K extends PropertyKey> = U extends any
? K extends keyof U
? U
: U & Record<K, unknown>
: never;
export const has = <T extends object, K extends PropertyKey>(
source: T,
property: K,
): source is Discriminate<T, K> =>
property in source;
然而,最后的测试没有通过。
对于上下文,我想这样做的原因是,我的自定义has
函数可以在编译时验证键(这是in
操作符没有做的事情)。我可以理解这一部分我正在努力的部分只是模仿in
执行的收缩。
2条答案
按热度按时间izj3ouym1#
如果没有单边或细粒度类型保护(如microsoft/TypeScript#14048中所要求的),支持保护的true和false端的独立行为,目前是不可能的。
你所要求的是接受一个联合类型
{ foo: string } | { bar?: number }
,并提出一个用户定义的类型保护,其中真分支将其缩小为{ bar?: number }
,假分支将其保留为完整的联合{ foo: string } | { bar?: number }
。阅读
checker.ts
的第19788到19809行中的getNarrowedType()
函数,编译器的类型检查器,我认为这不可能发生。现在,将用户定义的
(x: any) => x is G
类型的类型保护应用于 union typeT
的值,编译器当前将对类型保护的true分支和false分支的union类型进行 * 分区。所以在true分支中你会得到类似于Extract<T, G>
的东西,在false分支中你会得到类似于Exclude<T, G>
的东西。如果一个联合成员存在于真分支中,它将不存在于假分支中,反之亦然。只有当
T
是 not 联合类型时,真分支和假分支窄化才可能不互相排斥(有时你会得到真分支的T & G
,而假分支只有T
)。这并不完美(按照ms/TS#31156),但它就是这样。
iyr7buue2#
has
函数可以用一种缩小其参数类型的方式来编写,至少在键是可选的并且可以缩小为非可选的情况下:但是,您对
Union
类型的要求并不合理。请考虑以下示例: