【问题标题】:Incorrect return type for conditional generics条件泛型的返回类型不正确
【发布时间】:2022-01-19 19:47:56
【问题描述】:

我正在尝试编写一个使用泛型和条件类型返回值的函数,如下所示:

interface Foo<T> {
  bar: T extends true ? string : undefined;
  id: string;
}

interface Options<T> {
  withBar?: T;
}

function createFoo<T extends boolean>({ withBar }: Options<T>): Foo<T> {
  return {
    id: 'foo',
    ...(withBar && { bar: 'baz' }),
  };
}

上面会抛出以下类型错误:

Type '{ bar?: "baz" | undefined; id: string; }' is not assignable to type 'Foo<T>'.
  Types of property 'bar' are incompatible.
    Type '"baz" | undefined' is not assignable to type 'T extends true ? string : undefined'.
      Type 'undefined' is not assignable to type 'T extends true ? string : undefined'.

有人可以帮我理解为什么会这样吗?我指定类型可以是未定义的,所以应该允许它是未定义的。

此外,我想在给定某些参数的情况下获取函数的返回类型,而无需实际调用它。这可能吗?

例如ReturnType&lt;typeof createFoo&gt; 不会为我提供正确的用法类型 createFoo({ withBar: true }) 因为它还不知道用法。

Live example here

【问题讨论】:

  • “我指定类型可以是未定义的,所以应该允许它是未定义的。” 好吧...您具体说明在一个特定情况下它是未定义,并且 Typescript 无法确定您的代码确保特定情况由您的函数逻辑处理。 Typescript 很难验证函数的逻辑是否满足条件返回类型的约定。这对于编译器来说很难跟踪,因为它并没有真正执行你的代码。

标签: typescript


【解决方案1】:

这是一个常见问题,泛型类型在函数实现内部无法解析。您可以编写一个函数重载来使您的实现工作:

interface Foo<T> {
  bar: T extends true ? string : undefined;
  id: string;
}

interface Options<T> {
  withBar?: T;
}

function createFoo<T extends boolean>({ withBar }: Options<T>): Foo<T>
function createFoo({ withBar }: Options<boolean>): Foo<true> | Foo<false> {
  // The implementation works because the return type is allowed to be Foo<true> or Foo<false>
  return {
    id: 'foo',
    ...(withBar && { bar: 'baz' }),
  };
}

// The first function overload is taken because it's the first one to match
// the call parameters
const foo = createFoo({ withBar: false }); // foo is of type Foo<false>

TypeScript playground

【讨论】:

  • 你知道为什么使用Object.assign 可以在my answer 中使用,而扩展语法不起作用?
  • @jsejcksn Object.assign 从第二个参数复制可枚举的自己的属性。 false 的值(withBar &amp;&amp; xyz 的值/类型,如果 withBar 为假)没有任何这些,因此 JS 有效地对它执行任何操作而不会出错。类型签名只是创建了参数的交集,所以 TS 很好。 Spread 对可迭代对象进行操作,因此 TS 将键入错误,因为 false 不可迭代。
  • 太棒了!给定其中一种情况,我将如何获取返回类型?例如type Baz = ReturnType&lt;typeof createFoo&gt;; 我不能在那里传递泛型。鉴于我的真实世界场景,我还试图避免将 Foo 类型与泛型一起使用。
  • 计算结果为Foo&lt;boolean&gt;,这不是您所期望的吗?
  • 是的,但是我将如何使用我的语法来获得与Foo&lt;true&gt; 相同的语法?我不能type Baz = ReturnType&lt;typeof createFoo&gt;; 然后type Bam = Baz&lt;true&gt; 可以吗?
【解决方案2】:

必须的属性不能保存值或undefined(键始终存在)与可选属性(键可能根本不在对象中)之间有一个重要区别。见exactOptionalPropertyTypes

在您的帖子中,您在Foo 上键入bar 一如既往存在,条件值为stringundefined,但您返回的对象仅在条件下具有bar 键全部。

在下面的修改中,我重新键入了Foo,以反映基于相同布尔条件的bar 的可选属性类型。

我还用Object.assign(满足编译器)替换了扩展运算符语法,但是,我不确定为什么会有所不同。

另外,值得注意的是:如果您尝试将非对象类型传播到对象中(例如,undefined 在您的帖子中),TS 会抱怨:

const obj = {...undefined};
/*           ^^^^^^^^^^^^
Error: Spread types may only be created from object types.(2698) */

TS Playground

type StringId = { id: string };

type Foo<T> = T extends true ? StringId & { bar: string } : StringId;

interface Options<T> {
  withBar?: T;
}

function createFoo<T>({ withBar }: Options<T>): Foo<T> {
  return Object.assign({id: 'foo'}, (withBar && { bar: 'baz' }));
}

console.log(createFoo({withBar: true})); // { id: "foo", bar: "baz" }
console.log(createFoo({withBar: false})); // { id: "foo" }
console.log(createFoo({})); // { id: "foo" }

此外,我想在给定某些参数的情况下获取函数的返回类型,而无需实际调用它。这可能吗?

是的,通过在函数中添加条件返回类型,这取决于参数:

TS Playground

function createFoo<T extends boolean>({ withBar }: Options<T>): T extends true ? Foo<true> : Foo<false> {
  return Object.assign({id: 'foo'}, (withBar && { bar: 'baz' }));
}

const foo1 = createFoo({withBar: true}); // StringId & { bar: string }
const foo2 = createFoo({withBar: false}); // StringId

【讨论】:

  • 感谢info @chrisbajorin
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-14
相关资源
最近更新 更多