繁体   English   中英

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

[英]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()函数中,您可以执行以下任一操作:

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

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

  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 如果我需要在许多其他位置引用许多属性来使用(以防我需要重构事物)。

你可能注意到我没有谈论 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";
    }
}

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


更新 2

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

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

可运行示例


更新 3

尽管语法(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);

可运行示例

有限的重构

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

这意味着,如果您在赋值的任何部分拼错了属性名称(示例中的valuethis['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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM