【问题标题】:How to explicitly annotate the return type of a TypeScript mixin factory function?如何显式注释 TypeScript mixin 工厂函数的返回类型?
【发布时间】:2021-11-01 08:49:39
【问题描述】:

鉴于here 描述的示例 TypeScript 混合模式:

type Constructor = new (...args: any[]) => {};
 
// This mixin adds a scale property, with getters and setters
// for changing it with an encapsulated private property:
 
function Scale<TBase extends Constructor>(Base: TBase) {
  return class Scaling extends Base {
    // Mixins may not declare private/protected properties
    // however, you can use ES2020 private fields
    _scale = 1;
 
    setScale(scale: number) {
      this._scale = scale;
    }
 
    get scale(): number {
      return this._scale;
    }
  };
}

我们如何显式注释Scale 函数的返回类型?即填写???:

function Scale<TBase extends Constructor>(Base: TBase): ??? { 
  ...

【问题讨论】:

    标签: typescript mixins return-type


    【解决方案1】:

    您不需要为 Scale 函数声明显式返回类型,因为 TypeScript 很聪明,可以推断返回类型。将鼠标悬停在Scale 上,您会看到返回类型为

    {
        new (...args: any[]): Scaling;
        prototype: Scale<any>.Scaling;
    } & TBase
    

    此外,如果您想为Scale 使用显式返回类型,您应该在Scale 函数之外声明Scaling(内部类)。像这样:

     class Scaling extends Base {
    
        _scale = 1;
     
        setScale(scale: number) {
          this._scale = scale;
        }
     
        get scale(): number {
          return this._scale;
        }
      }
    
    function Scale<TBase extends Constructor>(Base: TBase) {
      return Scaling
    }
    

    但是Base 应该是静态的。

    这意味着我们应该创建Mixin函数,就像在docs - Alternative Pattern中一样:

    // credits goes to credits goes to https://stackoverflow.com/a/50375286
    type UnionToIntersection<U> =
        (U extends any ? (k: U) => void : never) extends (
            k: infer I
        ) => void
        ? I
        : never;
    
    type ClassType = new (...args: any[]) => any;
    
    function Mixin<T extends ClassType, R extends T[]>(...classRefs: [...R]):
        new (...args: any[]) => UnionToIntersection<InstanceType<[...R][number]>> {
        return merge(class { }, ...classRefs);
    }
    
    function merge(derived: ClassType, ...classRefs: ClassType[]) {
        classRefs.forEach(classRef => {
            Object.getOwnPropertyNames(classRef.prototype).forEach(name => {
                // you can get rid of type casting in this way
                const descriptor = Object.getOwnPropertyDescriptor(classRef.prototype, name)
                if (name !== 'constructor' && descriptor) {
                    Object.defineProperty(
                        derived.prototype,
                        name,
                        descriptor
                    );
                }
            });
        });
    
        return derived;
    }
    
    class Foo {
        tag = 'foo'
    }
    
    class Scaling extends Mixin(Foo) {
    
        _scale = 1;
    
        setScale(scale: number) {
            this._scale = scale;
        }
    
        get scale(): number {
            return this._scale;
        }
    }
    
    const result = new Scaling();
    
    result.tag // string
    result.scale // number
    

    您可以在medium 和in my blog 中找到我的文章的分步说明。

    UnionToIntersection - 创建联合类型的交集。您可以在answer中找到完整的解释

    ClassType - 几乎与您的Constructor 相同。它是任何类构造函数的类型。

    Mixin - 在 variadic tuple types 的帮助下推断参数中提供的每个类构造函数,并将其所有实例合并到一个对象 UnionToIntersection&lt;InstanceType&lt;[...R][number]&gt;&gt;。

    `[...R][number]` - takes a union of all provided class intstances
    `InstanceType<[...R][number]>>` - replace every class constructor in the union 
                                      with class instance accordingly
    `UnionToIntersection<InstanceType<[...R][number]>>` - merges all class instances
    

    merge - 与来自docs 的applyMixins 相同

    更新

    由于Scaling 是在函数内部定义的,因此不可能添加包含Scaling 的显式返回类型,因为它还不存在于作用域中。

    【讨论】:

    • 感谢@captain-yossarian - 在您对如何键入替代模式的描述中肯定有相当多的内容需要消化。但这里只关注最初的问题——是否可以为 TypeScript 提供显式类型注释以用于 Scale 函数的返回类型?也就是说,它在源代码中,而不是由编辑器工具描述。我确实理解 TS 通常可以推断类型(并且不一定要讨论何时最好这样做)。答案很可能是“不,这不可能”,这是完全公平且有趣的!
    • @MattR 很公平)我做了更新
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-23
    • 1970-01-01
    • 1970-01-01
    • 2018-04-08
    • 2021-08-07
    • 2021-04-19
    • 1970-01-01
    相关资源
    最近更新 更多