我想要TS中完全不可变的对象

时间:2017-03-30 13:56:24

标签: typescript typescript2.0

我有一些大的对象,比如

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

如何完全固定物体?是仅通过为每个深层嵌套对象创建“readonly”接口和接口?

5 个答案:

答案 0 :(得分:2)

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

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的功能。

但是如果你真的需要没有类型定义,我认为唯一的方法是定义一个冰箱(喜欢这个术语:D),就像Hampus建议的那样。取自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:你不能在没有定义类型的情况下获得编译器类型错误。这就是打字稿的重点。

编辑:

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

let a = 1
a = "hello"

将抛出错误,因为类型隐式设置为number。但是,对于readonly,我认为,您需要按照上面的定义进行适当的声明。

答案 1 :(得分:0)

查看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异常

答案 2 :(得分:0)

这有效:

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

答案 3 :(得分:0)

我们现在有了选项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)
}

答案 4 :(得分:0)

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