typescript - 跟踪 TypeScript 中的参数类型

标签 typescript types builder

我在 TypeScript 中为我的一些实体实现了 Builder 模式。这是其中之一(为简单起见已剥离),也是in the playground :

type Shape = any;
type Slide = any;
type Animation = any;

export class SlideBuilder {

  private slide: Slide;

  public static start() { return new SlideBuilder(); }

  public withShape(name: string, shape: Shape): this {
    this.slide.addShape(name, shape);
    return this;
  }

  public withAnimation(name: string, animation: Animation): this {
    this.slide.addAnimation(name, animation);
    return this;
  }

  public withOrder(shape: string, animations: string[]) {
    this.slide.addOrder(shape, animations);
    return this;
  }
}

SlideBuilder
  .start()
  .withShape("Hello World", {})
  .withAnimation("Animation1", {})
  .withAnimation("Animation2", {})
  .withOrder("Could be 'Hello World' only", ["Could be 'Animation1' or 'Animation2' only"])

问题是,我想添加一个输入检查 withOrder 的可能性。已使用正确的参数调用,参数已传递给 withShapewithAnimation .

我已经尝试将泛型类型添加到类中,例如:
export class SlideBuilder<S, A> {
  withShape(name: S, shape: Shape)
  withAnimation(name: A, animation: Animation)
  withOrder(shape: S, animation: A[])
}

但是我找不到跟踪每个调用的方法,例如将调用中的每种类型收集到联合类型中。我知道我需要以某种方式指定 withOrder(shape: S1 | S2 | S3 | ... | Sn)在哪里 Sn是来自 withShape 的类型调用,但实际如何实现呢?

最佳答案

这是一个很好的问题,回答起来很愉快!

我们如何让编译器跟踪类实例的方法在实例生命周期内收到的所有参数?

哇!这是一个很大的问题!起初我不确定这是否可能。

以下是编译器在类实例的生命周期内必须做的事情:

  • 在每个方法调用中,添加到实例接收到的参数集。
  • 对这些参数进行分组,以便我们稍后可以对它们进行类型检查。

  • 开始了...

    回答

    以下方法足够复杂,我只提供了方法签名。我还将这些签名简化为可以表达想法的最低要求。方法实现将为您提供相对简单的方法。

    该方法使用累加器类型来跟踪参数类型。这些累加器类型类似于我们将在 Array.reduce 中使用的累加器对象。功能。

    这里是 the playground link和代码:
    type TrackShapes<TSlideBuilder, TNextShape> = 
      TSlideBuilder extends SlideBuilder<infer TShapes, infer TAnimations> 
      ? SlideBuilder<TShapes | TNextShape, TAnimations> 
      : never;
    
    type TrackAnimations<TSlideBuilder, TNextAnimation> = 
      TSlideBuilder extends SlideBuilder<infer TShapes, infer TAnimations> 
      ? SlideBuilder<TShapes, TAnimations | TNextAnimation> 
      : never;
    
    export class SlideBuilder<TShape, TAnimation> {
    
      public static start(): SlideBuilder<never, never> {
        return new SlideBuilder<never, never>();
      };
    
      public withShape<TNext extends string>(name: TNext): TrackShapes<this, TNext> {
          throw new Error('TODO Implement withShape.');
      }
    
      public withAnimation<TNext extends string>(name: TNext): TrackAnimations<this, TNext> {
          throw new Error('TODO Implement withAnimation.');
      }
    
      public withOrder(shape: TShape, animation: TAnimation[]): this {
        throw new Error('TODO Implement withOrder.');
      }
    }
    

    那里发生了什么?

    我们为 SlideBuilder 定义了两种累加器类型。 .这些接收一个现有的SlideBuilder , infer它的形状和动画类型,使用类型联合来扩大适当的泛型类型,然后返回 SlideBuilder .这是答案中最高级的部分。

    然后里面start , 我们使用 never初始化 SlideBuilder为零(可以这么说)。这很有用,因为 T | never 的并集是 T (类似于 5 + 0 = 5 的方式)。

    现在每次调用withShapewithAnimation使用适当的累加器作为其返回类型。这意味着每次调用都会适本地扩展类型并将参数分类到适当的存储桶中!

    请注意 withShapewithAnimation泛型 extend string .这将类型限制为 string .它还可以防止将字符串文字类型扩大到 string .这意味着调用者不需要使用 as const从而提供更友好的 API。

    结果?我们“跟踪”参数类型!以下是一些测试,显示它如何满足要求。

    测试用例
    // Passes type checking.
    SlideBuilder
      .start()
      .withShape("Shape1")
      .withAnimation('Animation1')
      .withOrder("Shape1", ["Animation1"])
    
    // Passes type checking.
    SlideBuilder
      .start()
      .withShape("Shape1")
      .withAnimation('Animation1')
      .withAnimation('Animation2')
      .withOrder("Shape1", ["Animation1", "Animation2"])
    
    // Fails type checking.
    SlideBuilder
      .start()
      .withShape("Shape1")
      .withAnimation('Animation1')
      .withAnimation('Animation2')
      .withOrder("Foo", ["Animation1", "Animation2"])
    
    // Fails type checking.
    SlideBuilder
      .start()
      .withShape("Shape1")
      .withAnimation('Animation1')
      .withAnimation('Animation2')
      .withOrder("Shape1", ["Foo", "Animation2"])
    

    答案的演变

    最后,这里有一些游乐场链接,展示了这个答案的演变:

    Playground Link显示仅支持形状并需要 as const 的初始解决方案.

    Playground Link将动画带入类并仍在使用 as const .

    Playground Link不再需要 as const并提供了一个几乎完成的解决方案。

    关于typescript - 跟踪 TypeScript 中的参数类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61558542/

    相关文章:

    javascript - 可以在不使用 exports 命令的情况下编写此 typescript

    c++ - 生成器模式 : making sure the object is fully built

    python - 类型错误 : 'float' object is not callable

    text - 对于 ColdFusion cfqueryparam,您将哪种 CFSQLType 用于文本?

    java - HTTP Builder/Groovy - 丢失 302(重定向)处理?

    javascript - 使用 gulp 进行 typescript 编译

    typescript - jest typescript - 模拟日期构造函数

    javascript - 如何使绘图 Canvas 响应?

    types - Rust 中的枚举与结构类型名称如何工作?