可以像这样创建DeepReadonly
类型:
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
interface A {
B: { C: number; };
D: { E: number; }[];
}
const myDeepReadonlyObject: DeepReadonly<A> = {
B: { C: 1 },
D: [ { E: 2 } ],
}
myDeepReadonlyObject.B = { C: 2 }; // error :)
myDeepReadonlyObject.B.C = 2; // error :)
这很棒。 B
和B.C
都是只读的。当我尝试修改D
但是......
// I'd like this to be an error
myDeepReadonlyObject.D[0] = { E: 3 }; // no error :(
我应该如何编写DeepReadonly
以便嵌套数组也是只读的?
答案 0 :(得分:7)
从TypeScript 2.8开始,现在可以实现,并且实际上是条件类型的PR中的一个示例:https://github.com/Microsoft/TypeScript/pull/21316
另请参阅条件类型的类型推断注释: https://github.com/Microsoft/TypeScript/pull/21496
我稍微修改了示例以使用readonly数组值类型的类型推断,因为我发现(infer R)[]
比Array<T[number]>
更清晰,但两种语法都有效。我还删除了示例NonFunctionPropertyNames
位,因为我想在输出中保留函数。
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]>;
};
以这种方式执行DeepReadonly也会保留可选字段(感谢Mariusz让我知道),例如:
interface A {
x?: number;
y: number;
}
type RA = DeepReadonly<A>;
// RA is effectively typed as such:
interface RA {
readonly x?: number;
readonly y: number;
}
虽然在某些情况下TS仍然有一些简单的方法可以丢失“readonly-ness”,但这与你将得到的C / C ++样式const
值接近。
答案 1 :(得分:1)
您可以拥有只读数组:
interface ReadonlyArray<T> extends Array<T> {
readonly [n: number]: T;
}
let a = [] as ReadonlyArray<string>;
a[0] = "moo"; // error: Index signature in type 'ReadonlyArray<string>' only permits reading
但你不能在解决方案中使用它:
interface A {
B: { C: number; };
D: ReadonlyArray<{ E: number; }>;
}
myDeepReadonlyObject.D[0] = { E: 3 }; // still fine
D
的类型为DeepReadonly<ReadonlyArray<{ E: number; }>>
,不允许ReadonlyArray
启动。
我怀疑你是否能够使其适用于包含数组的对象,如果你想要一个通用的接口/类型而不是特定的接口/类型,你可以对数组或对象进行深度读取。
例如,这将正常工作:
interface A {
readonly B: { readonly C: number; };
D: ReadonlyArray<{ E: number; }>;
}
const myDeepReadonlyObject = {
B: { C: 1 },
D: [{ E: 2 }],
} as A;
myDeepReadonlyObject.B = { C: 2 }; // error
myDeepReadonlyObject.B.C = 2; // error
myDeepReadonlyObject1.D[0] = { E: 3 }; // error
但它有一个特定的界面(A
),而不是通用的DeepReadonly
。
另一种选择是使用内置定义文件附带的Immutable.js,它非常易于使用。
答案 2 :(得分:1)
您可能要使用ts-essentials软件包:
import { DeepReadonly } from "ts-essentials";
const myDeepReadonlyObject: DeepReadonly<A> = {
B: { C: 1 },
D: [ { E: 2 } ],
}
答案 3 :(得分:0)
DeepReadonly泛型是一个有价值的工具,因为它可以帮助强制不变性。
type DR<T> =
T extends Function ? T :
T extends ReadonlyArray<infer R> ? IDRArray<R> :
T extends Map<infer K, infer V> ? IDRMap<K, V> :
T extends object ? DRObject<T> :
T
interface IDRArray<T> extends ReadonlyArray<DR<T>> {}
interface IDRMap<K, V> extends ReadonlyMap<DR<K>, DR<V>> {}
type DRObject<T> = {
readonly [P in keyof T]: DR<T[P]>;
}
T extends ReadonlyArray<infer R> ?
对于Array<any>
和ReadonlyArray<any>
都是正确的。检查它是否扩展了Array将会错过ReadonlyArray。 答案 4 :(得分:0)
您可以使用ts-toolbelt,它可以对任何深度的类型进行操作
在您的情况下,它将是:
import {O} from 'ts-toolbelt'
interface A {
B: { C: number; };
D: { E: number; }[];
}
type optional = O.Readonly<A, keyof A, 'deep'>
如果您想对其进行深入的计算(出于显示目的),可以为此使用Compute
答案 5 :(得分:0)
除了zenmumbler answer之外,自TypeScript 3.7发布以来,现在还支持recursive type aliases,这使我们能够改进解决方案:
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
export type Immutable<T> =
T extends ImmutablePrimitive ? T :
T extends Array<infer U> ? ImmutableArray<U> :
T extends Map<infer K, infer V> ? ImmutableMap<K, V> :
T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
export type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
export type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
export type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
export type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
您可能会注意到,我们没有像传统的解决方案那样扩展基本接口,就像interface ImmutableArray<T> extends ReadonlyArray<Immutable<T>> {}
,而是像type ImmutableArray<T> = ReadonlyArray<Immutable<T>>
那样直接引用它们。
旧的解决方案在大多数情况下都可以很好地工作,但是由于替换了原始类型,因此几乎没有问题。例如,如果您使用immer并将ImmutableArray
的旧实现传递给produce
函数,则草稿将缺少像push()
这样的数组方法。
在GitHub上还有issue,可将DeepReadonly类型添加到TypeScript。