简介我希望能够为我的类用户创建只读属性(而不是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);

Runnable Example

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

Runnable Example

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.