简介我希望能够为我的类用户创建只读属性(而不是getter),但我需要在内部更新它们;有没有办法做到这一点,并允许在内部进行更改? (并确保…
问题
我希望能够为我的班级用户制作readonly属性(不是getter),但我需要在内部更新它们;有没有办法做到这一点,并允许在内部进行更改? (并确保TypeScript阻止大多数尝试更改值)
(在我的情况下,它是一个游戏引擎和getters / setters [一般的功能]是一个糟糕的选择)
4
投票
OP here发布的答案是最好的答案,而不是这个答案。这只是使用接口而不是导出它。
interface IGraphObjectInternal { _parent: GraphNode; }
export class GraphNode implements IGraphObjectInternal {
// tslint:disable-next-line:variable-name
// tslint:disable-next-line:member-access
// tslint:disable-next-line:variable-name
public readonly _parent: GraphNode;
public add(newNode: GraphNode) {
(newNode as IGraphObjectInternal)._parent = this;
}
}
我之前尝试过这个并遇到了一些问题(不确定原因,但现在再试一次,它运行得很好。
在这里留下答案只是为了玩它的乐趣。
TypeScript提供了readonly关键字,该关键字允许在初始化或仅在构造函数中设置值。
如果您想随时更改值,那么您需要的是只读属性,称为“get”属性。
例:
class MyClass {
private _myValue: WhateverTypeYouWant;
get myValue() {
return this._myValue;
}
doSomething(inputValue: WhateverTypeYouWant) {
// Do checks or anything you want.
this._myValue = inputValue; // Or anything else you decide
}
}
值得一提的是,用户仍然可以致电myObject[‘_myValue’]并访问该物业。但是,TypeScript不会在intellisense中告诉他们,如果他们以这种方式使用你的代码,他们会以不受支持的方式使用你的库并在脚下拍摄自己(注意这是客户端代码,所以代码可以阅读)。
检查the official documentation的工作原理。
更新
如果你真的想使用readonly并强制它工作,你可以这样做:
class Test {
readonly value = "not changed";
changeValue() {
this["value" as any] = "change from inside";
}
}
但正如我在对这个答案的评论中所提到的,并且我在runnable version of this example中显示,语义是相同的,如果用户真的想要,private和readonly都可以从外部改变。
更新2
在进一步的评论中,你带来了一个有趣的场景,游戏开发,其中函数调用被认为是昂贵我无法验证属性访问可能有多贵(这通常是推荐的路径),但这是我认为您正在寻找的答案:
如果你真的想要设置readonly成员,并且只想确保你有重构支持,请将this[“value” as any] =更改为(this.value as Test[‘value’]) =(其中Test这里是类名,value是属性名称)。
class Test {
// We had to set the type explicitly for this to work
// Because due to initial `= "not changed"`
// `value` property has type `"not changed"` not `string`
readonly value: string = "not changed";
changeValue() {
(this.value as Test['value']) = "change from inside";
alert(this.value);
}
}
const test = new Test();
test.changeValue();
(test.value as Test['value']) = 'change from outside';
alert(test.value);
更新3
虽然语法(this.value as Test[‘value’]) =可以在官方的TypeScript操作系统中运行,但正如本答案中Update 2末尾的链接所证明的那样,它在VS Code(以及其他TS环境)中不起作用。
您需要将其更改为this[‘value’ as Test[‘value’]] =(其中,Test是类名,value是属性名称)。
工作代码变为:
class Test {
// We had to set the type explicitly for this to work
// Because due to initial `= "not changed"`
// `value` property has type `"not changed"` not `string`
readonly value: string = "not changed";
changeValue() {
this['value' as Test['value']] = "change from inside";
alert(this.value);
}
}
const test = new Test();
test.changeValue();
test['value' as Test['value']] = 'change from outside';
alert(test.value);
Limited Refactoring
由于重构是提出问题的原因,我必须提到除了丑陋之外,这里的解决方法仅提供有限的重构支持。
这意味着,如果在任务value的任何部分拼错属性名称(样本中的this[‘value’ as Test[‘value’]] = …),TypeScript将给出编译时错误。
但问题是,至少在我的快速测试中的VS Code中,当您重命名属性时(从示例中的value到其他任何东西),TypeScript / VS Code不会更新使用此变通方法实现的对它的引用。
它仍然会给你一个编译时错误,这比没有错误的无效代码更好,但是你也希望它为你重命名属性。
幸运的是,必须使用字符串替换(样本中的[‘value’ as Test[‘value’]])似乎通常可以安全地避免错误匹配,但是,它仍然是愚蠢的,并且不尽如人意,但我认为这就是这个。
4
投票
实际上我知道有三种方法。如果您有这样的课程:
class GraphNode {
readonly _parent: GraphNode;
add(newNode: GraphNode) { /* ...etc... */ }
}
var node = new GraphNode();
在add()函数中你可以做到:
newNode[‘_parent’] = this; - 作品,但不好的想法。重构会破坏这一点。
(<{_parent: GraphNode}>newNode)._parent = this; - 优于1(不是最好的),虽然重构打破了它,但至少编译器会告诉你这次(因为类型转换会失败)。
BEST:创建一个INTERNAL接口(仅供您自己使用): interface IGraphObjectInternal { _parent: GraphNode; } class GraphNode implements IGraphObjectInternal { readonly _parent: GraphNode; add(newNode: GraphNode) { /* …etc… */ } } 现在你可以做(newNode)._parent = this;和重构也将工作。唯一需要注意的是,如果从命名空间导出类(使用内部接口IMO的唯一原因),您还必须导出接口。出于这个原因,我有时会使用#2完全锁定内部,其中只有一个地方使用它(而不是向世界做广告),但通常#3如果我需要有许多属性可以在许多其他位置引用(如果我需要重构的话)。
你可能会注意到我没有谈论getter / setters。虽然可以只使用getter而不使用setter,但是更新私有变量,TypeScript不会保护你!我可以很容易地做object[‘_privateOrProtectedMember’] = whatever,它会工作。它不适用于readonly修饰符(在问题中)。使用readonly修饰符可以更好地锁定我的属性(就在TypeScript编译器中工作而言),并且因为JavaScript没有readonly修饰符,我可以使用各种方法在JavaScript端使用变通方法更新它们(即在运行)。 ;)
警告:正如我所说,这只适用于TypeScript。在JavaScript中,人们仍然可以修改您的属性(除非您仅使用具有非公开属性的getter)。
Update
从typescript 2.8开始,你现在可以删除readonly修饰符:
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
以及可选的修饰符:
type Writeable<T> = { -readonly [P in keyof T]-?: T[P] };
更多这里:Improved control over mapped type modifiers
2
投票
从Typescript 2.8开始,你可以使用improved mapped type modifiers。
例如,假设UI层(以及除持久层之外的所有其他层)只能获得域实体的readonly版本。持久层是一种特殊情况,因为它必须知道如何将所有内部结构复制到数据库中。为了做到这一点,我们不希望每次都制作防御性副本,只是为此目的使用readonly打字稿修饰符。
您的readonly实体将是:
class Immutable {
constructor(
public readonly myProp: string ) {}
}
您实体的可变类型:
type Mutable = {
-readonly [K in keyof Immutable]: Immutable[K]
}
请注意删除标志的特殊-readonly语法(也适用于选项)。
在一个有限的地方(这里是持久层),我们可以通过以下方式将Immutable转换为Mutable:
let imm = new Immutable("I'm save here")
imm.myProp = "nono doesnt work. and thats good" // error
let mut: Mutable = imm // you could also "hard" cast here: imm as unknown as Mutable
mut.myProp = "there we go" // imm.myProp value is "there we go"
希望有所帮助。
-1
投票
解决方案1
使用readonly关键字。
它指示TypeScript编译器必须立即或在构造函数中设置成员。它与C#中的readonly具有相同的语法语义。
注意 - readonly在JavaScript级别不受尊重;它纯粹是一个编译器检查!
解决方案2
使用get-only访问者属性
Object.definedProperty(yourObject, "yourPropertyName", {
get: function() { return yourValue; }
enumerable: true,
configurable: false
});
解决方案3
使用只读值属性
Object.defineProperty(yourObject, "yourPropertyName", {
value: yourValue,
writable: false
});
解决方案4
使用Object.freeze来防止更改对象
class Foo {
constructor(public readonly name: string) {
Object.freeze(this);
}
}
注意,上面的代码确保名称在TypeScript中是readonly,并且不能在JavaScript中变异,因为该对象被冻结。
解决方案5
使用const
const x = 123;
x = 456 // Error! const declarations cannot be reassigned.