如何编写类型安全的联合类型化保存方法

Posted

技术标签:

【中文标题】如何编写类型安全的联合类型化保存方法【英文标题】:How to write a typesafe union typed save metod 【发布时间】:2021-02-18 02:31:12 【问题描述】:

我想创建一个通用的 http 服务,并且我想有一个类似 save(value) 的方法,它在下面调用 create(value)update(value)。不同的是,我想选择配置 create 接受的值的类型,它可以与更新值完全不同。

像这样:

class HttpService 
  create(model) 
    ...
  
  update(model) 
    ... 
  
  save(model) 
    if(model.id === null || model.id === undefined) 
      return this.create(model);
    
    return this.update(model);
  

this 的泛型是这样的:

class HttpService<I, E, C> 
  create(model: E | C) : E;
  update(model: E) : E;
  save(model: E | C): E;

我想合并这两种情况:

    创建和更新共享同一个接口,其中实体(称为E)ID 具有属性名称Id。这个 id 是通用的(称为I),它的类型是I | null | undefined。因此,此接口扩展了MaybeEntity&lt;I&gt;。 创建和更新接受不同的接口。当使用 ceate 函数时,它应该接受接口C extends NotEnity,其中Id 不存在,nullundefined 并返回E。此更新仅接受E extends Entity&lt;I&gt;,其中Id 必须为I

我已经尝试过这些类型,但我失败了

export type Id = 'id';
export type Entity<I> = Record<Id, I>;
export type NotEntity = Partial<Record<Id, null>>;
export type MaybeEntity<I> = Partial<Record<Id, I | null >>;

class HttpService<I, E extends MaybeEntity<I>, C extends NotEntity = (E & NotEntity )> 
  create(model: C | E & NotEntity)  

  update(model: E & Entity<I>) 

  save(model: C | E) 
    return model.id === undefined && model.id === null
      ? this.create(model)
      : this.update(model);
  

【问题讨论】:

打字稿种子typescriptlang.org/play?#code/… 【参考方案1】:

更新。抱歉,如果我错过了您的问题。我为Entity 创建了一个接口(尽管它可能是一个类型)。我还为NotEntity 创建了一个类型,它只是一个没有id 的实体。该类接受Entity,然后在保存函数中检查id 的类型,如果它是undefined 或其值等于null,那么我们正在讨论一个非实体并调用保存函数。否则我们有一个id,我们可以调用更新函数。我确信有更好的方法来做到这一点,但这可能是您自己的初步方法。

export interface Entity<I> 
  id?: I,
  [key: string]: any


export type NotEntity<I> = Omit<Entity<I>, 'id'>;

export class HttpService<I, E extends Entity<I>> 
  create(model: NotEntity<I>) 

  update(model: Entity<I>) 

  save(model: E) 
    return typeof model.id ===  undefined || model.id === null
      ? this.create(model)
      : this.update(model);
  

【讨论】:

【参考方案2】:

您的代码中缺少的是save 中的三元语句不理解它所施加的限制,并且没有改进truefalse 分支中model 的类型。

Typescript 可以理解简单的属性检查条件,但是当您将它们与 &amp;&amp; 结合使用时,它会停止尝试。我们需要将这个多重条件检查提取到它自己的类型保护函数中,并告诉 Typescript truefalse 是什么意思。

const hasId = <E extends MaybeEntity<any>>( model: E ): model is E & id: NonNullable<E['id']> => 
  return 'id' in model && model.id === undefined && model.id === null;

现在您允许C 要求创建所需但在创建的对象中不存在的任意属性。这真的有必要吗?可以支持这一点,但需要更高级的检查。

您还说如果存在id,则无法调用create。这真的有必要吗,还是可以忽略id? (例如,如果将现有对象克隆为新 id)。

假设这两个都不是绝对必要的,这段代码可以工作:

export class HttpService<I, E extends MaybeEntity<I>> 
  create(model: E)  

  update(model: E & Entity<I>)  

  save(model: E) 
    return hasId(model)
      ? this.update(model)
      : this.create(model);
  

Typescript Playground Link

【讨论】:

以上是关于如何编写类型安全的联合类型化保存方法的主要内容,如果未能解决你的问题,请参考以下文章

如何描述嵌套对象的类型?如何描述联合类型?

如何在 Play Framework 中编写“用户只能访问自己的个人资料页面”类型的安全检查?

TypeScript:推断嵌套联合类型的类型

如何在 cakephp 中编写内容类型?

如何使用泛型缩小 TypeScript 联合类型

如何定义“类型析取”(联合类型)?