DeepReadonly Object Typescript

让人想犯罪 __ 提交于 2019-11-28 12:06:32

As of TypeScript 2.8, this is now possible and actually an example in the PR for Conditional Types: https://github.com/Microsoft/TypeScript/pull/21316

Also see the notes on type inference for Conditional Types: https://github.com/Microsoft/TypeScript/pull/21496

I modified the example slightly to use the type inference for the readonly array value type because I find (infer R)[] clearer than Array<T[number]> but both syntaxes work. I also removed the example NonFunctionPropertyNames bit as I want to preserve functions in my output.

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

Doing DeepReadonly this way also preserves optional fields (thanks to Mariusz for letting me know), e.g.:

interface A {
    x?: number;
    y: number;
}

type RA = DeepReadonly<A>;

// RA is effectively typed as such:
interface RA {
    readonly x?: number;
    readonly y: number;
}

While TS still has some easy ways to lose "readonly-ness" in certain scenarios, this is as close to a C/C++ style const value as you will get.

You can have a readonly array:

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

But you can't use it with your solution:

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

myDeepReadonlyObject.D[0] = { E: 3 }; // still fine

The type of D is DeepReadonly<ReadonlyArray<{ E: number; }>> and it won't allow the ReadonlyArray to kick in.

I doubt that you'll manage to make it work to objects with arrays in them, you can have either deep read only for arrays or for objects if you want a generic interface/type and not specific ones.
For example, this will work fine:

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

But it has a specific interface to it (A) instead of a generic one DeepReadonly.

Another option is to use Immutable.js which comes with a builtin definition file and it's pretty easy to use.

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 } ],
}
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 generic is a valuable tool that can help enforce immutability.

  • I use the short DR name since I use this generic so often.
  • T extends ReadonlyArray<infer R> ? will be true for both Array<any> and ReadonlyArray<any>.

You can use ts-toolbelt, it can do operations on types at any depth

In your case, it would be:

import {O} from 'ts-toolbelt'

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

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

And if you want to compute it deeply (for display purposes), you can use Compute for that

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!