从传递函数的 return 值推断函数泛型类型 U

Infer function generic type U from return value of passed function

在大多数情况下,具有泛型类型的函数可以从参数中推断出泛型类型。但是,如果参数是泛型类型既是参数的一部分又是 return 值的函数,则有时无法推断出泛型类型。

带有用于存储项目的 class 的简化示例,其中项目的某些属性是自动生成的(例如,自动生成 ID 的数据库):

/**
 * Stores items of type T
 */
class Store<T> {

  /**
   * Return a function that creates items from supplied partial items merged with
   * attributes U auto-generated by a generator function
   */
  itemCreator<U>(
    generate: (item: Omit<T, keyof U>) => U
  ): (item: Omit<T, keyof U>) => Omit<T, keyof U> & U {
    return item => ({...item, ...generate(item)});
  }
}

type Person = {
  id: string;
  name: string;
  email: string;
  age?: number;
};

因此,如果您创建一个 Store<Person> 并提供一个生成器来自动生成 id,returned 创建函数将只需要 nameemail.

然而,在某些情况下,U 未被推断:

作品:

const create = new Store<Person>()
  .itemCreator(() => ({id: 'ID', extra: 42}));
// U is {id: string, extra: number}, `create` only needs to provide `name` and `email` :)
const person = create({name: 'John', email: 'john.doe@foo.com'}); // creates person with extra

无效:

const create = new Store<Person>()
  .itemCreator(item => ({id: 'ID', extra: 42}));
// U is now unknown, meaning the `create` function must provide complete `Person` objects :(
const person = create({name: 'John', email: 'john.doe@foo.com'}); // does not compile

适用于显式 <U>

const create = new Store<Person>()
  .itemCreator<{id: string, extra: number}>((item) => ({id: 'ID', extra: 42}));
const person = create({name: 'John', email: 'john.doe@foo.com'}); // creates person with extra

现在,将部分项目传递给生成器的原因是某些自动生成的属性可能依赖于其他属性(例如 id 作为 [=19= 的散列生成] 属性):

const creator = new Store<Person>()
  .itemCreator(item => ({id: hash(item.email)}))

所以,我的问题是,如果提供了 generate 函数的参数,为什么推断 U 会失败? TypeScript 是简单地使用 U 的第一个找到的实例还是什么原因? generate 函数 returns U,所以如果它看到 {id: string} 被 returned,有人可能会争辩说 U 也是 参数 generateOmit 类型应该是无关紧要的?

有办法解决吗?

@JHH、@Linda Paiste 您对下一个解决方案有何看法?:


class Store<T> {

    itemCreator<U>(
        generate: <P = Omit<T, keyof U>>(item: P) => U
    ): (item: Omit<T, keyof U>) => Omit<T, keyof U> & U {
        return item => ({ ...item, ...generate(item) });
    }
}

type Person = {
    id: string;
    name: string;
    email: string;
    age?: number;
};


const create = new Store<Person>()
    .itemCreator(item => {
        const x = item // Omit<Person, "id" | "extra">
        return ({ id: 'ID', extra: 42 })
    });
const person = create({ name: 'John', email: 'john.doe@foo.com' });


看起来TS很难推断item参数,所以我定义了默认值的extra generic。

Here,在我的博客中,你可以找到更多有趣的回调类型

Here 你可以找到 Titian Cernicova Dragomir 对这种行为的解释

If there is a parameter, the checker needs to decide U before checking the body, since there is no source for inference it goes with unknown. It then checks the body, but does not go back to try again with U = ret type it just checks the return compa. with U = unknown