It is possible to create a DeepReadonly type like this:
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 :)
This is great. Both B and B.C are readonly. When I try to modify D however...
// I'd like this to be an error
myDeepReadonlyObject.D[0] = { E: 3 }; // no error :(
How should I write DeepReadonly so that nested arrays are readonly as well?
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 bothArray<any>andReadonlyArray<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
来源:https://stackoverflow.com/questions/41879327/deepreadonly-object-typescript