[英]How can I change a readonly property in TypeScript?
我希望能够为我的班级用户创建readonly
属性(而不是 getter),但我需要在内部更新它们; 有没有办法做到这一点并允许在内部进行更改? (并确保 TypeScript 阻止大多数更改值的尝试)
(就我而言,它是一个游戏引擎,而 getter/setter [一般功能] 是一个糟糕的选择)
您可以使用自 Typescript 2.8 以来改进的映射类型修饰符。
例如,假设 UI 层(以及除持久层之外的所有其他层)只能获得域实体的readonly
版本。 持久层是一个特例,因为它必须知道如何将所有内部结构复制到数据库中。 为了做到这一点,我们不想每次都制作防御性副本,只需为此目的使用readonly
typescript 修饰符。
您的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"
希望有帮助。
我知道实际上有 3 种方法。 如果您有这样的课程:
class GraphNode {
readonly _parent: GraphNode;
add(newNode: GraphNode) { /* ...etc... */ }
}
var node = new GraphNode();
在add()
函数中,您可以执行以下任一操作:
newNode[<any>'_parent'] = this;
- 有效,但坏主意。 重构将打破这一点。
更新:似乎newNode['_parent'] = this;
在较新版本的 TypeScript 中没有<any>
可以正常工作,但重构仍然会破坏它。
(<{_parent: GraphNode}>newNode)._parent = this;
- 比 1 好(不是最好的),虽然重构破坏了它,但至少编译器这次会告诉你(因为类型转换会失败)。BEST:创建一个INTERNAL接口(仅供您自己使用):
interface IGraphObjectInternal { _parent: GraphNode; } class GraphNode implements IGraphObjectInternal { readonly _parent: GraphNode; add(newNode: GraphNode) { /* ...etc... */ } }
现在你可以做(<IGraphObjectInternal>newNode)._parent = this;
和重构也将起作用。 唯一需要注意的是,如果您从命名空间导出您的类(使用内部接口 IMO 的唯一原因),您还必须导出该接口。 出于这个原因,我有时会使用 #2 来完全锁定只有一个地方使用它的内部结构(而不是向全世界宣传),但通常 #3 如果我需要在许多其他位置引用许多属性来使用(以防我需要重构事物)。
你可能注意到我没有谈论 getter/setter。 虽然可以只使用 getter 而不使用 setter,然后更新私有变量,但 TypeScript 并不能保护您! 我可以很容易地做object['_privateOrProtectedMember'] = whatever
,它会起作用。 它不适用于readonly
修饰符(这是在问题中)。 使用readonly
修饰符可以更好地锁定我的属性(就在 TypeScript 编译器中工作而言),并且因为 JavaScript 没有readonly
修饰符,我可以使用各种方法通过 JavaScript 端的变通方法更新它们(即在运行时)。 ;)
警告:正如我所说,这只适用于 TypeScript。 在 JavaScript 中,人们仍然可以修改您的属性(除非您仅使用具有非公开属性的 getter)。
从 typescript 2.8 开始,您现在可以删除 readonly 修饰符:
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
以及可选的修饰符:
type Writeable<T> = { -readonly [P in keyof T]-?: T[P] };
更多信息: 改进对映射类型修饰符的控制
OP在这里发布的答案是最佳答案,而不是这个答案。 这只是使用接口而不是导出它。
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 不会在智能感知中告诉他们,如果他们确实以这种方式使用您的代码,那么他们正在以不受支持的方式使用您的库,并在脚下射击(请注意,无论如何这是客户端代码,因此代码可供阅读)。
查看官方文档了解其工作原理。
如果你真的想使用readonly
并强制它工作,你可以这样做:
class Test {
readonly value = "not changed";
changeValue() {
this["value" as any] = "change from inside";
}
}
但正如我在对此答案的评论中提到的,并且我在本示例的可运行版本中显示,语义是相同的,如果用户真的想要,可以从外部更改private
和readonly
。
在进一步的评论中,你带来了一个有趣的场景,游戏开发,其中函数调用被认为是昂贵的。 我无法验证属性访问可能有多昂贵(通常这是推荐的路径),但这是我认为您正在寻找的答案:
如果您真的真的想设置readonly
成员,并且只想确保您有重构支持,请将this["value" as any] =
更改为(this.value as Test['value']) =
(where 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);
尽管语法(this.value as Test['value']) =
在官方 TypeScript 操场上有效,正如本答案中更新 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);
由于重构是提出这个问题的原因,我不得不提到,除了丑陋之外,这里的解决方法仅提供有限的重构支持。
这意味着,如果您在赋值的任何部分拼错了属性名称(示例中的value
) this['value' as Test['value']] = ...
,TypeScript 会给您一个编译时错误。
这个问题虽然是,至少在VS代码在我的快速测试,当你(从命名属性value
的样品中的其他东西),打字稿/ VS代码不会更新使用此解决办法来实现对它的引用。
它仍然会给你一个编译时错误,这比让无效代码没有错误要好,但你也希望它为你重命名属性。
幸运的是,必须使用字符串替换(示例中的['value' as Test['value']]
)来执行此操作似乎通常可以避免错误匹配,但仍然很愚蠢,而且不太理想,但我认为到此为止。
我当前的 TypeScript 3.6.3 解决方案
type Mutable<T> = {
-readonly [k in keyof T]: T[k];
};
class Item {
readonly id: string;
changeId(newId: string) {
const mutableThis = this as Mutable<Item>;
mutableThis.id = newId;
}
}
这是我最喜欢的解决方案(使用 James Wilkins 提供的提示)。 我的想法是写访问应该只允许在类中,因此将 getter 设为私有。
type Writable<T> = { -readonly [K in keyof T]: T[K] };
class TestModel {
readonly val: number;
setVal(value: number) {
this.asWriteable.val = value;
}
// use this.asWriteable.* to write to readonly fields
private get asWriteable(): Writable<TestModel> {
return this as Writable<TestModel>;
}
}
解决方案1
使用readonly
关键字。
它指示 TypeScript 编译器必须立即设置该成员,或在构造函数中设置该成员。 它与 C# 中的readonly
具有相同的句法语义。
注意 - 在 JavaScript 级别不考虑readonly
; 这纯粹是一个编译器检查!
解决方案2
使用仅获取访问器属性
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);
}
}
注意,上面的代码确保 name 在 TypeScript 中是readonly
的,并且不能在 JavaScript 中改变,因为对象被冻结了。
解决方案5
使用const
const x = 123;
x = 456 // Error! const declarations cannot be reassigned.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.