代码之家  ›  专栏  ›  技术社区  ›  CaribouCode

条件泛型的返回类型不正确

  •  2
  • CaribouCode  · 技术社区  · 3 年前

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

    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'.
    

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

    此外,我想在不实际调用的情况下,获得给定某些params的函数的返回类型。这可能吗?

    例如 ReturnType<typeof createFoo> 不会给我正确的使用类型 createFoo({ withBar: true }) 因为它还不知道用法。

    Live example here

    1 回复  |  直到 3 年前
        1
  •  1
  •   Guerric P    3 年前

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

    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

        2
  •  1
  •   jsejcksn    3 年前

    所需财产与可持有价值的财产或 undefined (键始终存在)和一个可选属性(键可能根本不在对象中)。看见 exactOptionalPropertyTypes

    在你的帖子中,你键入 bar 在…上 Foo 始终存在,条件值为 string 未定义 ,但您返回的对象只有条件地具有 酒吧 关键。

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

    我还将排列运算符语法替换为 Object.assign (编译器满意),但是,我不确定为什么这会有什么不同。

    此外,值得注意的是:如果您试图将非对象类型扩展到对象中(例如。 未定义 在您的帖子中):

    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" }
    

    此外,我想在不实际调用的情况下,获得给定某些params的函数的返回类型。这可能吗?

    是的,通过向函数添加条件返回类型(取决于参数):

    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