如何更改 TypeScript 中的只读属性?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46634876/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 07:48:37  来源:igfitidea点击:

How can I change a readonly property in TypeScript?

typescript

提问by James Wilkins

I want to be able to make readonlyproperties (not getters) for users of my class, but I need to update them internally; is there a way to do this and allow to be changed internally? (and make sure TypeScript blocks most attempts to change the value)

我希望能够为readonly我的班级用户创建属性(而不是 getter),但我需要在内部更新它们;有没有办法做到这一点并允许在内部进行更改?(并确保 TypeScript 阻止大多数更改值的尝试)

(In my case it's a game engine and getters/setters [functions in general] are a bad option)

(就我而言,它是一个游戏引擎,而 getter/setter [一般功能] 是一个糟糕的选择)

采纳答案by James Wilkins

There are actually 3 ways I know of. If you have a class like this:

我知道实际上有 3 种方法。如果您有这样的课程:

class GraphNode {
    readonly _parent: GraphNode;
    add(newNode: GraphNode) { /* ...etc... */ }
}
var node = new GraphNode();

In the add()function you could do either:

add()函数中,您可以执行以下任一操作:

  1. newNode[<any>'_parent'] = this;- Works, but BAD IDEA. Refactoring will break this.

    Update:Seems newNode['_parent'] = this;will work just fine now without <any>in newer versions of TypeScript, but refactoring will still break it.

  2. (<{_parent: GraphNode}>newNode)._parent = this;- Better than 1 (not the best), and although refactoring breaks it, at least the compiler will tell you this time (since the type conversion will fail).
  3. BEST: Create an INTERNAL interface (used by yourself only):

    interface IGraphObjectInternal { _parent: GraphNode; }
    class GraphNode implements IGraphObjectInternal {
        readonly _parent: GraphNode;
        add(newNode: GraphNode) { /* ...etc... */ }
    }
    

    Now you can just do (<IGraphObjectInternal>newNode)._parent = this;and refactoring will also work. The only caveat is that if you export your class from a namespace (the only reason to use an internal interface IMO) you'll have to export the interface as well. For this reason, I sometimes will use #2 to completely lock down internals where there's only one place using it (and not advertise to the world), but usually #3 if I need to have many properties to work with referenced in many other locations (in case I need to refactor things).

  1. newNode[<any>'_parent'] = this;- 有效,但坏主意。重构将打破这一点。

    更新:newNode['_parent'] = this;现在似乎可以<any>在没有较新版本的 TypeScript 的情况下正常工作,但重构仍然会破坏它。

  2. (<{_parent: GraphNode}>newNode)._parent = this;- 比 1 好(不是最好的),虽然重构破坏了它,但至少编译器这次会告诉你(因为类型转换会失败)。
  3. BEST:创建一个INTERNAL接口(仅供您自己使用):

    interface IGraphObjectInternal { _parent: GraphNode; }
    class GraphNode implements IGraphObjectInternal {
        readonly _parent: GraphNode;
        add(newNode: GraphNode) { /* ...etc... */ }
    }
    

    现在你可以这样做(<IGraphObjectInternal>newNode)._parent = this;,重构也可以工作。唯一需要注意的是,如果您从命名空间导出您的类(使用内部接口 IMO 的唯一原因),您还必须导出该接口。出于这个原因,我有时会使用 #2 来完全锁定只有一个地方使用它的内部结构(而不​​是向全世界宣传),但通常 #3 如果我需要在许多其他位置引用许多属性来使用(以防我需要重构事物)。

You may notice I didn't talk about getters/setters. While it is possible to use only a getter and no setter, then update a private variable, TypeScript does not protect you! I can easily do object['_privateOrProtectedMember'] = whateverand it will work. It does not work for the readonlymodifier (which was in the question). Using the readonlymodifier better locks down my properties (as far as working within the TypeScript compiler is concerned), and because JavaScript doesn't have a readonlymodifier, I can use various methods to update them with workarounds on the JavaScript side (i.e. at runtime). ;)

你可能注意到我没有谈论 getter/setter。虽然可以只使用 getter 而不使用 setter,然后更新私有变量,但 TypeScript 并不能保护您!我可以轻松做到object['_privateOrProtectedMember'] = whatever,而且会奏效。它不适用于readonly修饰符(这是在问题中)。使用readonly修饰符可以更好地锁定我的属性(就在 TypeScript 编译器中工作而言),并且因为 JavaScript 没有readonly修饰符,我可以使用各种方法来更新它们,并在 JavaScript 端(即在运行时)使用变通方法. ;)

Warning:As I said, this only works within TypeScript. In JavaScript people can still modify your properties (unless you use getters only with non-exposed properties).

警告:正如我所说,这只适用于 TypeScript。在 JavaScript 中,人们仍然可以修改您的属性(除非您仅使用具有非公开属性的 getter)。

Update

更新

Since typescript 2.8 you can now remove the readonly modifiers:

从 typescript 2.8 开始,您现在可以删除 readonly 修饰符:

type Writeable<T> = { -readonly [P in keyof T]: T[P] };

and also the optionalmodifier:

以及可选的修饰符:

type Writeable<T> = { -readonly [P in keyof T]-?: T[P] };

More here: Improved control over mapped type modifiers

更多信息:改进对映射类型修饰符的控制

回答by ford04

You could make use of the improved mapped type modifierssince Typescript 2.8.

您可以使用自 Typescript 2.8 以来改进的映射类型修饰符

For example, let's say that UI layer (and all others except persistence layer) shall only get a readonlyversion of your domain entity. Persistence layer is a special case, since it somehow must know how to copy all internals into the database. In order to do so, we don't want to make defensive copies everytime and just use the readonlytypescript modifier for that purpose.

例如,假设 UI 层(以及除持久层之外的所有其他层)只能获得readonly域实体的一个版本。持久层是一个特例,因为它必须知道如何将所有内部结构复制到数据库中。为了做到这一点,我们不想每次都制作防御性副本,而只是readonly为此目的使用typescript 修饰符。

Your readonlyentity would be:

您的readonly实体将是:

class Immutable {
    constructor(
        public readonly myProp: string ) {}
}

The mutable type of your entity:

实体的可变类型:

type Mutable = {
     -readonly [K in keyof Immutable]: Immutable[K] 
}

Note the special -readonlysyntax to remove the flag(also works with optionals).

请注意-readonly删除标志的特殊语法(也适用于选项)。

In one limited place (here the persistence layer) we can convert Immutableto Mutableby doing:

在一个有限的地方(这里是持久层),我们可以通过执行以下操作转换ImmutableMutable

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"

Hope that helps.

希望有帮助。

回答by Meligy

The answer posted by OP hereis the best answer, not this one. Which is just to use an interface and not export it.

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;
  }
}

I tried this earlier and had some problem (not sure why, but tried again now and it works just fine.

Leaving the answer here just for the fun of playing with it.

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 provides readonlykeyword which allows setting value on initialization or in constructor only.

TypeScript 提供了readonly只允许在初始化或构造函数中设置值的关键字。

If you want to change the value any time, then what you need is a read-only property, which is known as a "get" property.

如果您想随时更改该值,那么您需要的是一个只读属性,它被称为“get”属性。

Example:

例子:

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
  }
}

It's worth mentioning that users may still be able to call myObject['_myValue']and access the property. TypeScript will not tell them about it in intellisense though, and if they do use your code this way, they are using your library in an unsupported way and shooting themselves in the foot (note that this is client-side code anyway, so the code is available to read).

值得一提的是,用户可能仍然可以调用myObject['_myValue']和访问该属性。但是,TypeScript 不会在智能感知中告诉他们,如果他们确实以这种方式使用您的代码,那么他们正在以不受支持的方式使用您的库,并在脚下射击(请注意,无论如何这是客户端代码,因此代码可供阅读)。

Check the official documentationon how this works.

查看官方文档了解其工作原理。



Update

更新

If you really want to use readonlyand force it to work, you can do it like this:

如果你真的想使用readonly并强制它工作,你可以这样做:

class Test {
    readonly value = "not changed";

    changeValue() { 
        this["value" as any] = "change from inside";
    }
}

But as I mentioned in my comment on this answer, and I show in the runnable version of this example, the semantics are the same in the sense that both privateand readonlycan be changed from outside if the users really want to.

但正如我在对此答案的评论中提到的,并且我在本示例可运行版本中显示,语义是相同的,如果用户真的想要privatereadonly可以从外部更改两者的语义。



Update 2

更新 2

In further comments you bring an interesting scenario, game development, where function call is considered expensive. I cannot validate how expensive property access might be (Which is the recommended path for this generally), but here's the answer I think you are looking for:

在进一步的评论中,你带来了一个有趣的场景,游戏开发,其中函数调用被认为是昂贵的。我无法验证属性访问可能有多昂贵(通常这是推荐的路径),但这是我认为您正在寻找的答案:

If you really really want to do set the readonlymember, and just want to make sure you have refactoring support, change this["value" as any] =to (this.value as Test['value']) =(where Testhere is the class name, and valueis the property name).

如果您真的真的想要设置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);

Runnable Example

可运行示例



Update 3

更新 3

Although the syntax (this.value as Test['value']) =works in official TypeScript playground, as proven by the link at the end of Update 2 in this answer, it doesn't work in VS Code (and maybe other TS environments).

尽管语法(this.value as Test['value']) =在官方 TypeScript 游乐场中有效,如本答案中更新 2 末尾的链接所证明的那样,但它在 VS Code(以及其他 TS 环境)中不起作用。

You need to change it to this['value' as Test['value']] =(where, again, Testis a class name and valueis a property name).

您需要将其更改为this['value' as Test['value']] =(这里再次Test是类名和value属性名)。

The working code becomes:

工作代码变为:

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);

Runnable Example

可运行示例

Limited Refactoring

有限的重构

Since refactoring is the reason for asking the question I have to mention that besides being ugly, the workaround here offers only limited refactoring support.

由于重构是提出这个问题的原因,我不得不提到,除了丑陋之外,这里的解决方法仅提供有限的重构支持。

That means, if you misspell property name (valuein the sample) in any part of the assignment this['value' as Test['value']] = ..., TypeScript will give you a compile time error.

这意味着,如果你value在赋值的任何部分拼错了属性名称(在示例中)this['value' as Test['value']] = ...,TypeScript 会给你一个编译时错误。

The problem though is, at least in VS Code in my quick test, when you rename the property (from valuein the sample to anything else), TypeScript / VS Code doesn't update the references to it that are implemented using this workaround.

但问题是,至少在我的快速测试中的 VS Code 中,当您重命名属性(从value示例中为其他任何内容)时,TypeScript / VS Code 不会更新使用此解决方法实现的对它的引用。

It still gives you a compile time error, which is better than leaving invalid code without errors, but you'd want it to rename the property for you too.

它仍然会给你一个编译时错误,这比让无效代码没有错误要好,但你也希望它为你重命名属性。

Luckily having to do this with a string replace (of ['value' as Test['value']]in the sample) seems to be generally safe from false matches, but still, it's silly, and less than desired, but I think this is as far as this gets.

幸运的是,必须使用字符串替换(['value' as Test['value']]示例中的of )来执行此操作似乎通常可以避免错误匹配,但仍然很愚蠢,而且比预期的要少,但我认为就这样了。

回答by Scott Schafer

Here's the solution I like best (using a tip courtesy of James Wilkins). My thought is that write access should be only allowed within the class, hence making the getter private.

这是我最喜欢的解决方案(使用 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>;
  }
}

回答by Reinhard

My current solution for TypeScript 3.6.3

我当前的 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;
  }
}

回答by Matthew Layton

Solution 1

解决方案1

Use the readonlykeyword.

使用readonly关键字。

It instructs the TypeScript compiler that the member must be set immediately, or within the constructor. It has the same syntactic semantics as readonlyin C#.

它指示 TypeScript 编译器必须立即或在构造函数中设置成员。它具有与readonlyC# 中相同的句法语义。

Note - readonlyis not respected at the JavaScript level; it's purely a compiler check!

注意 -readonly在 JavaScript 级别不受尊重;这纯粹是一个编译器检查!

Solution 2

解决方案2

Use a get-only accessor property

使用仅获取访问器属性

Object.definedProperty(yourObject, "yourPropertyName", {
    get: function() { return yourValue; }
    enumerable: true,
    configurable: false
});

Solution 3

解决方案3

Use a read-only value property

使用只读值属性

Object.defineProperty(yourObject, "yourPropertyName", {
    value: yourValue,
    writable: false
});

Solution 4

解决方案4

Use Object.freezeto prevent changes to your object

使用Object.freeze以防止更改你的对象

class Foo {
    constructor(public readonly name: string) {
        Object.freeze(this);
    }
}

Note, the code above ensures that name is readonlyin TypeScript, and cannot be mutated in JavaScript because the object is frozen.

请注意,上面的代码确保 namereadonly在 TypeScript 中,并且不能在 JavaScript 中改变,因为对象被冻结。

Solution 5

解决方案5

Use const

const

const x = 123;
x = 456 // Error! const declarations cannot be reassigned.