【问题标题】:I want totally immutable object in TS我想要 TS 中完全不可变的对象
【发布时间】:2020-03-25 05:35:38
【问题描述】:

我有一些大物件,比如

const a={
 b:33, 
 c:[78, 99], 
 d:{e:{f:{g:true, h:{boom:'selecta'}}}};/// well, even deeper than this...

我希望 TS 允许我这样做

a.d.e.f.h.boom='respek';

我怎样才能完全改变对象?是否仅通过为每个深度嵌套的对象创建具有“只读”和接口的接口?

【问题讨论】:

标签: typescript typescript2.0


【解决方案1】:

我们现在有了as const 选项,这是@phil294 提到的第一个选项(嵌套readonly)的语法简洁方式。

const a = {
    b: 33,
    c: [78, 99],
    d:{e:{f:{g:true, h:{boom:'selecta'}}}}
} as const;

a.d.e.f.h.boom = 'respek'; //Cannot assign to 'boom' because it is a read-only property.ts(2540)

作为额外的奖励,您可以使用以下技巧使嵌套函数的输入不可变:

type Immutable<T> = {
    readonly [K in keyof T]: Immutable<T[K]>;
}

这会发生

const a = {
    b: 33,
    c: [78, 99],
    d:{e:{f:{g:true, h:{boom:'selecta'}}}}
}

function mutateImmutable(input: Immutable<typeof a>) {
    input.d.e.f.h.boom = 'respek'; //Cannot assign to 'boom' because it is a read-only property.ts(2540)
}

【讨论】:

【解决方案2】:

https://www.typescriptlang.org/docs/handbook/interfaces.html 中所述,您可以在类/接口属性上使用readonly,或在不可变对象和数组上使用Readonly&lt;...&gt;/ReadonlyArray&lt;&gt;。在您的情况下,这将如下所示:

const a: Readonly<{
    b: number,
    c: ReadonlyArray<number>,
    d: Readonly<{
        e: Readonly<{
            f: Readonly<{
                g: boolean,
                h: Readonly<{
                    boom: string
                }>
            }>
        }>
    }>
}> = {
        b: 33,
        c: [78, 99],
        d:{e:{f:{g:true, h:{boom:'selecta'}}}}
}

a.d.e.f.h.boom = 'respek'; // error: Cannot assign to 'boom' because it is a constant or a read-only property.

显然,这是一个重言式的陈述,所以我建议你为你的对象定义适当的类结构。通过声明一个嵌套的、无类型的对象,您并没有真正利用 Typescript 的任何功能。

但如果你真的需要不使用类型定义,我认为唯一的方法是像 Hampus 建议的那样定义一个冷冻机(喜欢这个词:D)。取自deepFreeze(obj)函数from MDN

function freezer(obj) {
    Object.getOwnPropertyNames(obj).forEach(name => {
        if (typeof obj[name] == 'object' && obj[name] !== null)
            freezer(obj[name]);
    });
    return Object.freeze(obj);
}

const a = freezer({
    b:33, 
    c:[78, 99], 
    d:{e:{f:{g:true, h:{boom:'selecta'}}}}});

a.d.e.f.h.boom='respek'; // this does NOT throw an error. it simply does not override the value.

tl;dr:你不能在不定义类型的情况下得到编译器类型错误。这就是 Typescript 的全部意义所在。

编辑:

最后这句话是错误的。例如,

let a = 1
a = "hello"

会抛出错误,因为类型被隐式设置为数字。但是,对于只读,我认为您将需要如上定义的正确声明。

【讨论】:

    【解决方案3】:

    Minko Gechev 已创建 DeepReadonly 类型:

    type DeepReadonly<T> =
      T extends (infer R)[] ? DeepReadonlyArray<R> :
      T extends Function ? T :
      T extends object ? DeepReadonlyObject<T> :
      T;
    
    interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
    
    type DeepReadonlyObject<T> = {
      readonly [P in keyof T]: DeepReadonly<T[P]>;
    };
    
    interface Person {
      name: string;
      job: { company: string, position:string };
    }
    
    const person: DeepReadonly<Person> = {
      name: 'Minko',
      job: {
        company: 'Google',
        position: 'Software engineer'
      }
    };
    
    person.job.company = 'Alphabet'; // Error
    

    【讨论】:

      【解决方案4】:

      看看https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freezehttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

      Object.freeze 可能会做你想做的事,但至少 WebStorm 在尝试编辑对象时不会警告你,Chrome 会默默地失败。

      const obj = { val: 1 }; 
      Object.freeze(obj);
      obj.val = 2;
      
      console.log(obj);
      -> { val: 1 }
      

      不能在冻结对象的属性集中添加或删除任何内容。任何这样做的尝试都会失败,无论是静默还是抛出 TypeError 异常

      【讨论】:

      • 不适用于嵌套对象:let a = Object.freeze({ b: { c: 'c' } }); a.b.c = 'd'; a.b
      • 不仅对嵌套对象不起作用,而且不是TS的一部分,我需要TS编译器错误。
      • 好吧,可以建造一个冷冻机,也可以冷冻嵌套对象:)
      • @shal 我不知道 typescript 对变量的任何不变性支持。您可以在写入冻结对象时添加检查样式规则以进行警告。
      • 我知道我可以通过使用readonly 为每个属性定义接口并为每个嵌套对象创建接口来做到这一点,但这是一场噩梦,工作量太大......
      【解决方案5】:

      这行得通:

      const a= new class {
          readonly b = 33, 
          readonly c:ReadonlyArray<number> = [78, 99], 
          readonly d = new class {
              readonly e = new class {
                  readonly f = new class {
                      readonly g:true, 
                      readonly h: new class {
                          readonly boom:'selecta'}}}};

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-08
        • 1970-01-01
        • 2016-08-02
        • 1970-01-01
        • 2018-06-04
        • 1970-01-01
        • 2021-04-18
        • 1970-01-01
        相关资源
        最近更新 更多