我想要 TS 中完全不可变的对象
I want totally immutable object in TS
我有一些大物件,比如
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" 和接口?
看看https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze or https://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 异常
如 https://www.typescriptlang.org/docs/handbook/interfaces.html 中所述,您可以对 class/interface 属性使用 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.
显然,这是一个同义反复的陈述,所以我建议您为您的对象定义适当的 class 结构。仅仅通过声明一个嵌套的、无类型的对象,你并没有真正利用 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"
会抛出错误,因为类型隐式设置为数字。但是,我认为对于只读,您将需要如上定义的正确声明。
这个有效:
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'}}}};
我们现在有选项 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)
}
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
我有一些大物件,比如
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" 和接口?
看看https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze or https://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 异常
如 https://www.typescriptlang.org/docs/handbook/interfaces.html 中所述,您可以对 class/interface 属性使用 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.
显然,这是一个同义反复的陈述,所以我建议您为您的对象定义适当的 class 结构。仅仅通过声明一个嵌套的、无类型的对象,你并没有真正利用 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"
会抛出错误,因为类型隐式设置为数字。但是,我认为对于只读,您将需要如上定义的正确声明。
这个有效:
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'}}}};
我们现在有选项 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)
}
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