如何在 JavaScript/VSCode/TypeScript 中声明 setter 的类型?

问题描述 投票:0回答:4

我有一个非常简单的例子来演示这个问题:

class Person {
    _name = '';
    _age  = 0;
    get name() {
        return this._name;
    }
    /**
     * @type {string}
     */
    set name(name) {
        this._name = name;
    }
    get age() {
        return this._age;
    }
    /**
     * @type {number | string}
     */
    set age(age) {
        if (age === 'too old') {
            age = 100000;
        }
        this._age = age;
    }
}

我使用 VSCode 进行类型检查,但为什么它在类型上失败?

我明确表示年龄设置器可以采用数字或字符串:

enter image description here

javascript typescript jsdoc
4个回答
0
投票

您没有定义类型,而是编写了 JSDoc 注释。这实际上并不影响您的代码。因此 TypeScript 隐式设置类型

number

如果您希望设置器接受

string
number
,一种解决方案是使用如下联合类型:

set age(age: string | number) {
   ...
}

请记住,稍后设置

this._age
时会遇到问题,因为
this._age
也隐式具有类型
number
(默认值
0
),因此无法分配
string | number
类型的值。


0
投票

我相信您需要将联合类型括在括号中才能在 JSDoc 中有效:

/**
* @type {(number | string)}
*/

来自文档

多种类型(类型联合)这可以是数字或 boolean.{(number|boolean)} 这意味着一个值可以有以下几种之一 类型,将整个类型列表括在括号中,并且 用 | 分隔。


0
投票

以下代码片段应该可以工作。只需使用

age
检查
typeof
的类型即可将类型缩小为
if
语句。

class Person {
  _name = '';
  _age  = 0;
  get name() {
      return this._name;
  }
  /**
   * @param {string} name
   */
  set name(name) {
      this._name = name;
  }
  get age() {
      return this._age;
  }
  /**
   * @param {number | string} age
   */
  set age(age) {
      if (typeof age === 'string') {
        if (age === 'too old') {
          age = 100000;
        } else {
          age = parseInt(age)
        }
      }
      this._age = age;
  }
}

0
投票

解决办法其实很简单。

问题是

getter
setter
都是
function
类型的属性,尽管它们作为单个属性工作,但值可能是函数或任何其他。

问题在于您将

Person.name
的文档符号重新定义为
string
,而不是像正确的
function
所期望的那样定义它。你在
setter
女巫应该回来
void
做到了。

要简单地解决此问题,请尝试使用

@returns {string}
而不是
@type {string}

完整解决方案如下:

      class Person
      {
         /**
          * The parameters in the constructor are private to each instance.
          * 
          * @param { string } Name The parameter `Name` requires `string`.
          */
         constructor ( Name )
         {
            /**
             * The definition of the property must happen in the constructor
             * to protect it's private handling conditions.
             */
            Reflect.defineProperty
            (
               this, 'Name',
               {
                  /**
                   * Blocking the configuration of this property protects it
                   * from being overriden and hijacked out of control.
                   */
                  configurable: false,
                  
                  /**
                   * When defining a property through `getter` and `setter`
                   * they are not enumerable, so `Property in Person` does
                   * not capture the `Person.Name` in `Property`.
                   * 
                   * `{ ... Person }` will also not access `Person.Name`.
                   */
                  enumerable: true,
                  
                  /**
                   * This will not be exposed to `Person.Name`.
                   * 
                   * @returns { Name } Same type as the `Name` parameter.
                   */
                  get: () => Name,
                  
                  /**
                   * This will not be exposed to `Person.Name`.
                   * 
                   * @param { Name } New Same type as the `Name` parameter.
                   * @returns { void }
                   */
                  set: ( New ) => { Name = String(New).toString() },
               }
            );
            
            /**
             * The next part is necessary to parse the `Name` parameter given
             * by the user inside the `Person.Name` property's `setter`.
             */
            
            /**
             * `Person.Name` will only access this.
             * 
             * @type { Name }
             */
            this.Name = Name;
         }
         
         /**
          * The next part is unsafe because the descriptor of `Person.Age`
          * can be replaced and the property can be hijacked.
          */
         
         /**
          * This is indirectly exposed through `Person.Age`.
          * 
          * `Person._Age` is undefined if not setted, but it is still
          * exposed directly in the object instance. The editor can't
          * figure it's type because it was not documented.
          * 
          * But the return value is typed, so when getting `Person.Age`
          * the interpreter will expect `number`.
          * 
          * @returns { number } Will return `Person._Age`.
          */
         get Age ()
         {
            return this._Age;
         }
         
         /**
          * This is indirectly exposed through `Parson.Age`.
          * 
          * `Person._Age` is setted here and by default. This means
          * the property can be directly changed or have its descriptor
          * fully replaced to avoid the number conversion.
          * 
          * When setting `Person.Age = Value` the interpreter will
          * expect `Value` to be `number` because its the reference
          * for the `New` parameter witch is also `number`.
          * 
          * @param { number } New Replaces `Person.Age` after parsing.
          * @returns { void } Setters don't return anything.
          */
         set Age ( New )
         {
            this._Age = Number(New);
         }
      }
      
      const Human = new Person;
      
      Human.Name = 'Some Doe';
      Human.Age = 33;
      
      // Hijacking the `Person._Age` property.
      Human._Age = 'tirty two';
      
      console.log({ ... Human });

我在某些时候变得懒惰了,但是这个片段应该没有问题。

© www.soinside.com 2019 - 2024. All rights reserved.