DeepReadonly对象打字稿

Bra*_*ell 14 immutability typescript

可以创建这样的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 :)
Run Code Online (Sandbox Code Playgroud)

这很棒.这两个BB.C是只读的.当我尝试修改D但是...

// I'd like this to be an error
myDeepReadonlyObject.D[0] = { E: 3 }; // no error :(
Run Code Online (Sandbox Code Playgroud)

我应该怎么写,DeepReadonly以便嵌套数组也是只读的?

zen*_*ler 14

从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]>;
};
Run Code Online (Sandbox Code Playgroud)

以这种方式执行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;
}
Run Code Online (Sandbox Code Playgroud)

虽然在某些情况下TS仍然有一些简单的方法可以丢失"readonly-ness",但这const与你将获得的C/C++风格值接近.


Krz*_*zor 11

You might want to use ts-essentials package for that:

import { DeepReadonly } from "ts-essentials";

const myDeepReadonlyObject: DeepReadonly<A> = {
  B: { C: 1 },
  D: [ { E: 2 } ],
}
Run Code Online (Sandbox Code Playgroud)


Val*_*kov 8

除了 zenmumbler answer 之外,由于 TypeScript 3.7 发布,现在支持递归类型别名,它允许我们改进解决方案:

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]> };
Run Code Online (Sandbox Code Playgroud)

您可能会注意到,不像旧解决方案那样扩展基本接口,例如interface ImmutableArray<T> extends ReadonlyArray<Immutable<T>> {},我们直接引用它们,例如type ImmutableArray<T> = ReadonlyArray<Immutable<T>>

旧的解决方案在大多数情况下效果很好,但由于替换了原始类型,因此几乎没有问题。例如,如果您使用immer并将 的旧实现传递ImmutableArrayproduce函数,则草案将缺少像push().

还有一个问题有关添加DeepReadonly类型打字稿GitHub上。


小智 8

我认为这是一个更好的解决方案:

type DeepReadonly<T> = {
    readonly [P in keyof T]: DeepReadonly<T[P]>
}
Run Code Online (Sandbox Code Playgroud)

  • 通过额外的支持信息可以改进您的答案。请[编辑]添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以[在帮助中心](/help/how-to-answer)找到有关如何写出好的答案的更多信息。 (2认同)

Nit*_*mer 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
Run Code Online (Sandbox Code Playgroud)

但你不能将它与你的解决方案一起使用:

interface A {
    B: { C: number; };
    D: ReadonlyArray<{ E: number; }>;
}

myDeepReadonlyObject.D[0] = { E: 3 }; // still fine
Run Code Online (Sandbox Code Playgroud)

的类型DDeepReadonly<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
Run Code Online (Sandbox Code Playgroud)

但它有一个特定的接口 ( A) 而不是通用接口DeepReadonly

另一种选择是使用Immutable.js,它带有内置定义文件,并且非常易于使用。