如何解决带有联合的打字稿交集导致不存在的属性
在下面的示例中,我定义了 Typescript 类型以从索引请求数据。
有两种高效的方法可以从索引服务器检索数据块,通过startKey、endKey 或 by startKey,limit(键数)。
在将这些替代案例组合起来以在 Typescript 中定义请求时,我做错了,我看不出是什么,除非我与联合相交的方法没有意义,或者我不理解 typescript 错误。
>interface StartKey {
startKey: string;
}
interface EndKey {
endKey: string;
}
interface Limit {
limit: number;
}
type KeyRange = StartKey & EndKey;
type KeyLimit = StartKey & Limit;
type KeyBounds = KeyRange | KeyLimit;
export type Options = {
someparam:string
} & KeyBounds;
function retrieve(options:Options){
const {
startKey,endKey,//this line causes a compiler error
limit,//this line causes a compiler error
} = options;
}
首先,我创建了两个备用接口 KeyRange(具有 endKey)和 KeyLimit(有限制)。然后我将这些接口合并为一个 KeyBounds 类型。然后,在编写请求时,该 KeyBounds 类型通过与其他索引请求特定参数的交集进行组合。例如,使用 Options 请求项目应该能够使用一种或另一种策略来限制返回的结果。
This playground 显示了我目前采用的方法以及我从 Options 的定义中得到的令人惊讶的(对我而言)错误...
- “选项”类型上不存在属性“endKey”。
- “选项”类型不存在属性“限制”。
我希望有某些路径来获得 endKey OR 限制,因为 Options 包括具有这些属性的类型的联合。至多其中一个在任何时候都会出现,但这就像拥有一个可选属性一样,不会引发编译器错误。
导致错误的解构正是当我试图明确验证已请求哪个备用密钥边界签名时(我期望未设置一个或其他属性)。
相比之下,这些明确可选的代码将解构 NOT 视为错误情况,即使 endKey 和 limit 对于任何特定对象都可能未定义。我期望与联合的交集会产生类似的数据结构,除非编译器知道可能存在 endKey XOR 限制。
interface KeyRange {
startKey:string
endKey?:string
limit?:string
}
function retrieve(range:KeyRange){
const {
startKey,limit,} = range;
}
得到一个在结果类型上根本不存在的错误(甚至不是可选的)让我感到惊讶,这表明我错过了一些东西。谁能告诉我我需要做什么才能使这些替代品有效?
解决方法
一般来说,您不能访问联合类型值的属性,除非已知该属性键存在于联合的每个成员中:
interface Foo {
foo: string;
}
interface Bar {
bar: string;
}
function processFooOrBar(fooOrBar: Foo | Bar) {
fooOrBar.foo; // error!
// Property 'foo' does not exist on type 'Foo | Bar'.
// Property 'foo' does not exist on type 'Bar'
}
错误信息有点误导。当编译器抱怨“属性 foo
在类型 Foo | Bar
上不存在”时,它实际上意味着“属性 foo
不已知存在于输入 Foo | Bar
”。该属性当然可能存在,但由于 Bar
类型的值不一定具有这样的属性,因此编译器会警告您。
如果您有一个联合类型的值并且想要访问仅存在于联合的某些成员上的属性,则需要通过类型保护对值进行某种类型的 this other stack overflow answer。例如,您可以使用 in
运算符作为类型保护(由 narrowing 实现):
if ("foo" in fooOrBar) {
fooOrBar.foo.toUpperCase(); // okay
} else {
fooOrBar.bar.toUpperCase(); // okay
}
在您的情况下,这意味着将您的解构分为两种情况:
let startKey: string;
let endKey: string | undefined;
let limit: number | undefined;
if ("endKey" in options) {
const { startKey,endKey } = options;
} else {
const { startKey,limit } = options;
}
(这个 in
类型保护很有用,但在技术上是不安全的,因为对象类型在 TypeScript 中是开放和可扩展的。可以像这样获得一个带有 Bar
属性的 foo
对象:
const hmm = { bar: "hello",foo: 123 };
processFooOrBar(hmm); // no error at compiler time,but impl will error at runtime
所以要小心...但实际上这种情况很少发生)
您可以处理此问题的另一种方法是在进行解构之前扩展为具有显式可选属性的类型。您已将此作为一种解决方法,但您无需触摸 Options
类型本身。只需将 options
值从 Options
扩大到类似 StartKey & Partial<EndKey & Limit>
:
const widerOptions: StartKey & Partial<EndKey & Limit> = options;
const {
startKey,endKey,limit,} = widerOptions;
最后,您可以将 Options
重写为明确的“XOR”版本,其中编译器知道如果您检查联合的“错误”一侧的属性,则值将是 undefined
:
type XorOptions = {
startKey: string,endKey?: never,limit: number,someParam: string
} | {
startKey: string,endKey: string,limit?: never,someParam: string
}
这与您的 Options
不同之处在于 XorOptions
联合的每个成员都明确提及每个属性。然后你可以毫无问题地解构:
function retrieve2(options: XorOptions) {
const {
startKey,} = options;
}
,
基于@jcalz 的建议,我定义了 KeyBounds,通过引入 Xor 类型,它可以交替由 endKey 或限制满足,但不能同时满足。这会强制 未使用 路径(A 或 B)中的属性名称具有 never 类型,而不是简单地没有定义。
一旦有一个定义,即使它有时解析为从不,那么解构就可以毫无错误地发生......
type Xor<A,B> =
| (A & { [k in keyof B]?: never })
| (B & { [k in keyof A]?: never });
interface StartKey {
startKey: string;
}
interface EndKey {
endKey: string;
}
interface Limit {
limit: number;
}
type KeyBounds = StartKey & Xor<EndKey,Limit>;
export type Options = {
someparam:string
} & KeyBounds;
function retrieve(options:Options){
const {
startKey,} = options;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。