typescript DeepReadonly对象类型脚本

v1l68za4  于 2023-05-23  发布在  TypeScript
关注(0)|答案(9)|浏览(175)

可以像这样创建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 :)

太棒了BB.C都是只读的。当我尝试修改D时...

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

我应该如何编写DeepReadonly以使嵌套数组也是只读的?

bgibtngc

bgibtngc1#

从TypeScript 2.8开始,这现在是可能的,实际上是条件类型PR中的一个例子:https://github.com/Microsoft/TypeScript/pull/21316
另请参阅条件类型的类型推断注解:https://github.com/Microsoft/TypeScript/pull/21496
我稍微修改了这个例子,对只读数组值类型使用类型推断,因为我发现(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值。

qxsslcnc

qxsslcnc2#

除了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]> };

您可能注意到,我们没有像旧的解决方案那样扩展基本接口,比如interface ImmutableArray<T> extends ReadonlyArray<Immutable<T>> {},而是直接引用它们,比如type ImmutableArray<T> = ReadonlyArray<Immutable<T>>
旧的解决方案在大多数情况下都能很好地工作,但是由于替换了原始类型,所以几乎没有问题。例如,如果使用immer并将ImmutableArray的旧实现传递给produce函数,则草案将缺少像push()这样的数组方法。
GitHub上也有issue,关于将DeepReadonly类型添加到TypeScript。

t30tvxxf

t30tvxxf3#

您可能需要使用ts-essentials包来实现此功能:

import { DeepReadonly } from "ts-essentials";

const myDeepReadonlyObject: DeepReadonly<A> = {
  B: { C: 1 },
  D: [ { E: 2 } ],
}
azpvetkf

azpvetkf4#

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

type DeepReadonly<T> = {
    readonly [P in keyof T]: DeepReadonly<T[P]>
}
flvtvl50

flvtvl505#

export type DR<T> = DeepReadonly<T>

type DeepReadonly<T> =
// tslint:disable-next-line: ban-types
    T extends  AnyFunction | Primitive ? T :
    T extends ReadonlyArray<infer R> ? IDRArray<R> :
    T extends ReadonlyMap<infer K, infer V> ? IDRMap<K, V> :
    T extends ReadonlySet<infer ItemType>? ReadonlySetDeep<ItemType>:
    T extends object ? DRObject<T> :
    T

export type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint

export type AnyFunction = (...args: any[]) => any

interface IDRArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DRObject<T> = {
    readonly [P in keyof T]: DeepReadonly<T[P]>;
}

interface IDRMap<K, V> extends ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> {}

interface ReadonlySetDeep<ItemType>
    extends ReadonlySet<DeepReadonly<ItemType>> {}

DeepReadonly泛型是一个有价值的工具,可以帮助实现不可变性。

  • 我使用短DR名称,因为我经常使用这个泛型。
  • T extends ReadonlyArray<infer R> ?对于Array<any>ReadonlyArray<any>都为真。
628mspwn

628mspwn6#

您可以使用只读数组:

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,它带有一个内置的定义文件,非常容易使用。

r6hnlfcb

r6hnlfcb7#

可以使用ts-toolbelt它可以在任意深度对类型进行操作
在你的情况下,它将是:

import {O} from 'ts-toolbelt'

interface A {
  B: { C: number; };
  D: { E: number; }[];
}

type optional = O.Readonly<A, keyof A, 'deep'>

如果您想深入计算它(出于显示目的),可以使用Compute来实现

aydmsdu9

aydmsdu98#

type DeepReadonly<T> = {
    readonly [Key in keyof T]: T[Key] extends any[] | Record<string, unknown> ? DeepReadonly<T[Key]> : T[Key]
}
unftdfkk

unftdfkk9#

现在您可以只使用as const,因为它对所有嵌套对象都是只读的
根据https://github.com/microsoft/TypeScript/issues/31856
下面是一个示例https://www.typescriptlang.org/play?#code/MYewdgzgLgBAhjAvDA3gKBpmY4FsCmAXDAIwA0GWcA5kapVljgceQ4-LcQEwUdYATOFDjEA2gF12AXzTT4EGKEhQ0auADoa+DUJEaADgFcIACwAUJAJQBuNJu0bm+JDADMdoA

相关问题