我有一个非常简单的例子来演示这个问题:
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 进行类型检查,但为什么它在类型上失败?
我明确表示年龄设置器可以采用数字或字符串:
您没有定义类型,而是编写了 JSDoc 注释。这实际上并不影响您的代码。因此 TypeScript 隐式设置类型
number
。
如果您希望设置器接受
string
和 number
,一种解决方案是使用如下联合类型:
set age(age: string | number) {
...
}
请记住,稍后设置
this._age
时会遇到问题,因为 this._age
也隐式具有类型 number
(默认值 0
),因此无法分配 string | number
类型的值。
我相信您需要将联合类型括在括号中才能在 JSDoc 中有效:
/**
* @type {(number | string)}
*/
来自文档:
多种类型(类型联合)这可以是数字或 boolean.{(number|boolean)} 这意味着一个值可以有以下几种之一 类型,将整个类型列表括在括号中,并且 用 | 分隔。
以下代码片段应该可以工作。只需使用
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;
}
}
解决办法其实很简单。
问题是
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 });
我在某些时候变得懒惰了,但是这个片段应该没有问题。