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

时间:2017-10-08 18:55:11

标签: typescript

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

(在我的情况下,它是游戏引擎和getters / setters [一般的功能]是一个糟糕的选择)

6 个答案:

答案 0 :(得分:3)

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

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

add()功能中,你可以这样做:

  1. newNode[<any>'_parent'] = this; - 工作,但不好意思。重构会破坏这一点。
  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如果我需要有许多属性来使用引用在许多其他地方(如果我需要重构的话)。

  4. 您可能会注意到我没有谈论过吸气剂/安装者。虽然可以只使用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] };
    

    更多信息:Improved control over mapped type modifiers

答案 1 :(得分:3)

  

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中显示,语义是相同的,因为privatereadonly都可以从如果用户真的想在外面。

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

有限重构

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

这意味着,如果您在作业value的任何部分拼错了属性名称(示例中为this['value' as Test['value']] = ...),TypeScript将为您提供编译时错误。

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

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

幸运的是,必须使用字符串替换(样本中的['value' as Test['value']])似乎通常可以安全地避免错误匹配,但是,它仍然是愚蠢的,并且不尽如人意,但我认为这是目前为止因为这会得到。

答案 2 :(得分:2)

自打字稿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"

希望有帮助。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

答案 5 :(得分:-1)

解决方案1 ​​

使用readonly关键字。

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

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

解决方案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.