【问题标题】:Caching constructor causing "Property '...' has no initializer and is not definitely assigned in the constructor."导致“Property '...' 的缓存构造函数没有初始化程序,并且未在构造函数中明确分配。”
【发布时间】:2018-02-03 20:55:36
【问题描述】:

在我的项目中,我时不时地创建带有构造函数的类,这些构造函数缓存它们创建的对象,这样如果构造函数被多次调用时使用相同的参数,它每次都返回相同的实例,而不是创建一个新的实例与已创建的相同。

这是一个最小的例子:

class X {
    private static __cache: Record<string, X> = Object.create(null);

    readonly name: string; // The compilation error happens on this line.

    constructor(name: string) {
        const cached = X.__cache[name];
        if (cached !== undefined) {
            return cached;
        }

        this.name = name;
        X.__cache[name] = this;
    }
}

这段代码在 TypeScript 上工作得很好,直到我移到 2.7 并打开 strictPropertyInitialization 。现在我在readonly name: string; 上收到一个错误,说

属性“名称”没有初始化器,也没有在构造函数中明确分配。

我的项目中有多个具有上述模式的类,因此我需要提出一个或多个通用解决方案来消除错误。

两种我不想要的解决方案:

  1. 关闭strictPropertyInitialization。我发现它一般来说太有用了,不能关闭它。打开它会显示一些需要更新的定义,以更好地反映我的某些类的工作方式,或者提示初始化代码的改进。

  2. name 添加明确的赋值断言,使其声明为readonly name!: string;。感叹号导致 TypeScript 不再检查是否明确分配了 name。这消除了错误,但它也在编译器检查我的口味中打了一个太大的洞。例如,如果我使用断言并且我不小心在上面的代码中删除了赋值 this.name = name,那么 TypeScript 不会引发错误。我喜欢尽早收到错误通知。

我在上面展示了一个最小的示例,但在我的应用程序中,我的类具有更多字段,或者是通过非常昂贵的计算创建的字段,而不仅仅是从构造函数参数中分配的。

【问题讨论】:

    标签: typescript constructor


    【解决方案1】:

    对于对象的字段计算成本很高的情况,我目前首选的解决方案是将constructor 标记为private(在某些情况下可能会指示protected)并将工厂函数声明为类的静态成员。像这样:

    class X2 {
        private static __cache: Record<string, X2> = Object.create(null);
    
        readonly name: string;
    
        private constructor(nameSource: string) {
            this.name = expensiveComputation(nameSource);
        }
    
        // We use this factory function to create new objects instead of
        // using `new X2` directly.
        static make(name: string): X2 {
            const cached = X2.__cache[name];
            if (cached !== undefined) {
                return cached;
            }
    
            return X2.__cache[name] = new X2(name);
        }    
    }
    

    因为构造函数总是设置它的所有字段,TypeScript 不再有问题。这就要求使用类的代码使用工厂函数来创建新对象,而不是直接使用构造函数。

    【讨论】:

      【解决方案2】:

      对于构造函数仅将其参数分配给字段而不执行任何重要计算的情况,我使用的另一种解决方案是在检查是否已创建实例之前仅翻转构造函数中的逻辑以执行字段分配。如果事实证明一个实例已经存在,则可能不必要地进行字段分配,但这不是我要担心的事情,直到分析表明它在实际应用程序中是一个真正的问题。

      看起来像这样:

      class X {
          private static __cache: Record<string, X> = Object.create(null);
      
          readonly name: string;
      
          constructor(name: string) {
              // Set the fields first...
              this.name = name;
      
              // And then figure out whether we have an instance to return.
              const cached = X.__cache[name];
              if (cached !== undefined) {
                  return cached;
              }
      
              X.__cache[name] = this;
          }
      }
      

      使用参数属性,它甚至可以简化为:

      class X {
          private static __cache: Record<string, X> = Object.create(null);
      
          // The parameter on the constructor also defines the property on 
          // instances of X.
          constructor(readonly name: string) {
      
              // And then figure out whether we have an instance to return.
              const cached = X.__cache[name];
              if (cached !== undefined) {
                  return cached;
              }
      
              X.__cache[name] = this;
          }
      }
      

      【讨论】:

        【解决方案3】:

        https://www.ryadel.com/en/ts2564-ts-property-has-no-initializer-typescript-error-fix-visual-studio-2017-vs2017

        如果问题发生在我们自己的代码中,我们能做的最好的事情是通过添加明确的赋值断言修饰符来手动修复它,就像上面解释的那样:在大多数情况下,一堆 !放置在正确的位置将足以修复您的项目。

        【讨论】:

          猜你喜欢
          • 2021-06-23
          • 2019-06-03
          • 1970-01-01
          • 2021-04-13
          • 2021-05-21
          • 2021-05-06
          • 2021-05-09
          • 2021-09-04
          • 2021-08-14
          相关资源
          最近更新 更多