【问题标题】:Typescript Conditional Types Not Figuring Out Type打字稿条件类型没有弄清楚类型
【发布时间】:2022-01-18 22:34:33
【问题描述】:

Playground link

假设给定类型:

export declare type MongoManagerFilter<T> = {
  [P in keyof T]?: (P extends keyof T ? T[P] : any);
};

尝试指定过滤器时:

update.$setOnInsert.createdAt = new Date();

我收到此错误:

Type 'Date' is not assignable to type '"createdAt" extends keyof T ? T[keyof T & "createdAt"] : any'.ts(2322)

但如果我将过滤器更改为此它可以工作:

export declare type MongoManagerFilter<T> = {
  [P in keyof T]?: any;
};

为什么条件语句实际上并没有弄清楚类型?它只是将代码保留为类型。

我知道过滤器没有意义,我有另一种类型而不是 keyof T 来捕获点表示法,但我遇到了同样的问题,条件语句无法确定实际类型。

完整示例

interface CollectionDocument {
  _id?: string;
  createdAt: Date;
}

type PathImpl<T, Key extends keyof T> =
  Key extends string
  ? T[Key] extends Record<string, any>
    ? `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string>}`
      | `${Key}.${Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string}`
    : never
  : never;

type Path<T> = keyof T | PathImpl<T, keyof T>;

type PathValue<T, P extends Path<T>> =
    P extends `${infer Key}.${infer Rest}`
    ? Key extends keyof T
      ? Rest extends Path<T[Key]>
        ? PathValue<T[Key], Rest>
        : never
      : never
    : P extends keyof T
      ? T[P]
      : any;

export declare type MongoManagerFilter<T> = {
    [P in Path<T>]?: PathValue<T, P>;
};

export class MongoManagerCollection<T extends CollectionDocument> {
  constructor() {}
  
  updateOne(filter: MongoManagerFilter<T>) {
    // THIS DOES NOT WORK
    filter._id = '';
    filter.createdAt = new Date();
  }
}

// THIS WORKS
let testModel = new MongoManagerCollection<CollectionDocument>();
testModel.updateOne({_id: 'test', createdAt: new Date()});

编辑 向这个游乐场添加另一层以解决更多问题:

Playground link

由于类型的递归,我似乎也遇到了这个错误:

Type instantiation is excessively deep and possibly infinite.ts(2589)

【问题讨论】:

  • 请提供minimal reproducible example,清楚地表明您面临的问题。理想情况下,有人可以将代码放入像The TypeScript Playground (link here!) 这样的独立 IDE 中,然后立即着手解决问题,而无需首先重新创建它。所以不应该有伪代码、拼写错误、不相关的错误或未声明的类型或值。
  • 正在处理中
  • 感觉像一个错误,虽然我真的不明白你打算用这种情况做什么。如果PT 的一个键,那么P 什么时候不会从T 扩展一个键?感觉条件总是为真,因此相当于[P in keyof T]?: T[P];。这会很好。我知道你写了“我知道过滤器没有意义”,但它很难帮助你找出问题。
  • 我会调整它以添加点符号

标签: typescript typescript-typings typescript-generics


【解决方案1】:

泛型类型T in:

export class MongoManagerCollection<T extends CollectionDocument> {
  constructor() { }

  updateOne(filter: MongoManagerFilter<T>) {
    // THIS DOES NOT WORK
    filter._id = '';
    filter.createdAt = new Date();
  }
}

是一个黑匣子。 filter._id 被解析为非评估条件类型。将其视为非调用函数。从未调用过的函数不返回任何值。 filter._id 也是如此。 filter._id 对此作出回应:

type PathValue<T, P extends Path<T>> =
  P extends `${infer Key}.${infer Rest}`
  ? Key extends keyof T
  ? Rest extends Path<T[Key]>
  ? PathValue<T[Key], Rest>
  : never
  : never
  : P extends keyof T ////////////////////////////////////////////////
  ? T[P]              // Property is resolved as a conditional type //
  : any;              ///////////////////////////////////////////////

您可能已经注意到,filter._idP extends keyof T ? T[P] : any 而不仅仅是 T[P]

有一个解决方法。您可以使用T[P &amp; keyof T] 代替conditional type。 请参阅工作示例:

interface CollectionDocument {
  _id?: string;
  createdAt: Date;
}

type PathImpl<T, Key extends keyof T> =
  Key extends string
  ? T[Key] extends Record<string, any>
  ? `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string>}`
  | `${Key}.${Exclude<keyof T[Key], keyof Date | keyof Object | keyof string> & string}`
  : never
  : never;

type Path<T> = keyof T | PathImpl<T, keyof T>;

type PathValue<T, P extends Path<T>> =
  P extends `${infer Key}.${infer Rest}`
  ? Key extends keyof T
  ? Rest extends Path<T[Key]>
  ? PathValue<T[Key], Rest>
  : never
  : never
  : T[P & keyof T]

export declare type MongoManagerFilter<T> = {
  [P in Path<T>]?: PathValue<T, P>;
};

export class MongoManagerCollection<T extends CollectionDocument> {
  constructor() { }

  updateOne(filter: MongoManagerFilter<T>) {
    filter._id = ''; // ok
    filter.createdAt = new Date(); // ok
    filter.createdAt = 'sdf'; // expected error

  }
}

// THIS WORKS
let testModel = new MongoManagerCollection<CollectionDocument>();
testModel.updateOne({ _id: 'test', createdAt: new Date() });

Playground

如果您对path 的实现感兴趣,可以查看this 的答案、this utility 和/或我的article

我不知道 MongoDB api 和你的类型的要求,所以我不能说它们是否需要修改。

此外,您可以摆脱泛型:

export class MongoManagerCollection {
  constructor() { }

  updateOne(filter: MongoManagerFilter<CollectionDocument>) {
    filter._id = ''; // ok
    filter.createdAt = new Date(); // ok

  }
}

// THIS WORKS
let testModel = new MongoManagerCollection();
testModel.updateOne({ _id: 'test', createdAt: new Date() });

但我认为这不是你想要的。

【讨论】:

  • 我将再添加一个游乐场,以更进一步。我的过滤器无法正常工作以进行更新。添加到我所有的 mongo 类型中。
猜你喜欢
  • 2019-05-03
  • 2022-01-22
  • 2021-11-09
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 2021-09-18
  • 1970-01-01
  • 2021-09-05
相关资源
最近更新 更多