Wzorce
Jak zapobiec mutacji poniżej pierwszego poziomu właściwości, zamiast zamrażać tylko zewnętrzny typ obiektu?
Zachowaj funkcje, przejdź rekurencyjnie przez tablice i zmapuj każdą właściwość obiektu z modyfikatorem readonly.
Przepis
type DeepReadonly<T> = T extends (...args: any[]) => unknown ? T : T extends readonly unknown[] ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T type Config = { api: { url: string; retries: number } tags: string[] format: (value: number) => string } type FrozenConfig = DeepReadonly<Config>
Build to kompiluje i sprawdza każdy wynik poniżej.
Jak to działa
- 01
T extends (...args: any[]) => unknown
Funkcje są już wartościami wywoływalnymi; mapowanie ich składowych obiektowych zniszczyłoby sygnaturę wywołania.
- 02
{ readonly [K in keyof T]: DeepReadonly<T[K]> }Każda właściwość jest zamrażana, a jej wartość ponownie przechodzi przez tę samą decyzję.
Co dostajesz
FrozenConfig['api']→{ readonly url: string; readonly retries: number; }FrozenConfig['tags']→readonly string[]FrozenConfig['format']→(value: number) => string
Gdzie to zawodzi
To niezmienność w czasie kompilacji, nie `Object.freeze`. Traktuje też instancje klas jak obiekty strukturalne, co może zaskakująco ujawnić składowe implementacji; w kodzie domenowym dodaj jawne przypadki typów wbudowanych i klas.
Wniosek
Rekurencyjne modyfikatory potrzebują reguł zatrzymania. Obsłuż funkcje i obiekty specjalne przed ogólną gałęzią obiektu.
