带联合的Typescript交集导致不存在的属性 [英] Typescript intersection with a union leads to non-existent properties

查看:69
本文介绍了带联合的Typescript交集导致不存在的属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的示例中,我定义了Typescript类型以从索引中请求数据.

In the example below I define Typescript types to request data from an index.

有两种从索引服务器检索数据的有效方法,通过 endKey ,通过 startKey limit (键数).

There are two performant ways to retrieve a chunk of data from the index server, either by startKey,endKey or by startKey,limit (a count of keys).

当将这些替代情况组合在一起以在Typescript中定义请求时,我做错了事,我看不到什么,除非我与联合相交的方法没有道理或我不理解打字稿错误.

I'm doing something wrong when combining these alternate cases to define requests in Typescript and I can't see what, unless my approach to intersect a union doesn't make sense or I don't understand typescript errors.

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 (具有 limit ).然后,我将这些接口合并为 KeyBounds 类型.然后,在编写请求时,该 KeyBounds 类型通过与其他特定于索引请求的参数相交而组合在一起.例如,使用 Options 请求项目应该能够使用一种或另一种策略来限制返回的结果.

First of all I create the two alternate interfaces KeyRange (which has endKey) and KeyLimit (which has limit). Then I union those interfaces into a KeyBounds type. That KeyBounds type is then combined by intersection with other index-request-specific parameters when composing a request. For example requesting items using Options should be able to use either one or the other strategy to limit the returned results.

这操场显示我目前采用的方法和从选项的定义中得到的令人惊讶的(对我来说)错误...

This playground shows the approach I'm currently taking and the surprising (to me) errors that I get from the definition of Options...

  • 选项"类型上不存在属性"endKey".
  • 选项"类型上不存在属性限制".

我希望会有 some 条路径来获取endKey OR限制,因为Options包含具有这些属性的类型的并集.最多一次可以同时显示其中的一个,但这就像具有可选属性一样,不会引发编译器错误.

I would expect there to be some path to get endKey OR limit, since Options includes a union of types which have those properties. At most one of them will be present at any one time, but that's just like having an optional property, which doesn't throw a compiler error.

导致错误的结构破坏恰好是在我试图明确验证已请求哪个替代键界签名时(我希望未设置一个或其他属性).

The destructuring which causes the error is exactly when I'm trying to explicitly verify which of the alternate key bounds signatures has been requested, (I'm expecting one or other property to be unset).

相比之下,即使对于任何特定对象可能未定义endKey和limit,此代码在其显式可选的情况下也将解构NOT视为错误情况.我期望与联合的交集会导致类似的数据结构,除非编译器知道可能存在endKey XOR 限制.

By contrast, this code where they are explicitly optional treats the destructuring NOT as an error case, even though the endKey and limit might both be undefined for any particular object. I'm expecting an intersection with a union to result in a similar data structure, except the compiler knows there might be an endKey XOR a limit.

interface KeyRange {
  startKey:string
  endKey?:string
  limit?:string
}

function retrieve(range:KeyRange){
  const {
    startKey,
    endKey,
    limit,
  } = range;
}

在结果类型上根本不存在错误(甚至不是可选),这令我感到惊讶,并表明我错过了一些东西.谁能告诉我我需要做什么才能使这些候补有效?

Getting an error that neither exists at all on the resulting type (not even optionally) is surprising to me and suggests I've missed something. Can anyone tell me what I need to do to make these alternates valid?

推荐答案

通常,除非已知该属性键存在于的每个成员中,否则无法访问联合类型值上的属性.联合:

In general you cannot access a property on a union-typed value unless that property key is known to exist in every member of the union:

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 "时,条形码"这实际上表示"未知属性 foo Foo |类型存在.条形码".该属性当然可能存在,但是由于 Bar 类型的值不一定具有这样的属性,因此编译器会警告您.

The error message is a little misleading. When the compiler complains that "property foo does not exist on type Foo | Bar" it really means "the property foo is not known to exist in a value of type Foo | Bar". It is certainly possible for the property to exist, but because a value of type Bar does not necessarily have such a property, the compiler warns you.

如果您具有联合类型的值,并且想要访问仅在联合的某些成员上存在的属性,则需要执行某种

If you have a value of a union type and want to access properties that exist on only some members of the union, you need to do some sort of narrowing of the value via a type guard. For example, you can use the in operator as a type guard (as implemented by microsoft/TypeScript#15256):

  if ("foo" in fooOrBar) {
    fooOrBar.foo.toUpperCase(); // okay
  } else {
    fooOrBar.bar.toUpperCase(); // okay
  }

在您的情况下,这意味着将解构分为两种情况:

In your case that would mean splitting your destructuring into two cases:

  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 属性是这样的:

(This in type guard is useful but technically unsafe because object types are open and extendible in TypeScript. It is possible to get a Bar object with a foo property like this:

const hmm = { bar: "hello", foo: 123 };
processFooOrBar(hmm); // no error at compiler time, but impl will error at runtime

所以要小心...但是实际上这种情况很少发生)

so be careful... but in practice this happens rarely)

您可以处理此问题的另一种方法是在执行解构之前将其扩展为具有显式可选属性的类型.您已经在解决此问题,但是您不需要自己触摸 Options 类型本身.只需将 options 的值从 Options 扩展到类似 StartKey&部分< EndKey&限制> :

The other way you could deal with this is to widen to a type which has explicit optional properties before doing the destructuring. You are already doing this as a workaround, but you don't need to touch the Options type itself. Just widen the options value from Options to something like StartKey & Partial<EndKey & Limit>:

const widerOptions: StartKey & Partial<EndKey & Limit> = options;    
const {
  startKey,
  endKey,
  limit,
} = widerOptions;


最后,您可以将 Options 重写为显式的"XOR".编译器知道的版本,如果您检查错误"的属性并集的一侧的值将是 undefined :


Finally, you can rewrite Options to be explicitly an "XOR" version where the compiler knows that if you check the property on the "wrong" side of the union the value will be undefined:

type XorOptions = {
  startKey: string,
  endKey?: never,
  limit: number,
  someParam: string
} | {
  startKey: string,
  endKey: string,
  limit?: never,
  someParam: string
}

这与您的 Options 不同,因为 XorOptions 联合的每个成员都明确提及每个属性.然后,您可以毫无问题地进行破坏:

This differs from your Options in that every member of the XorOptions union has an explicit mention of every property. Then you can destructure without issue:

function retrieve2(options: XorOptions) {
  const {
    startKey,
    endKey,
    limit,
  } = options;
}

Playground link to code

这篇关于带联合的Typescript交集导致不存在的属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆