type DeepReadonly<T> = T extends (...args: never[]) => unknown
? T
: T extendsobject
? { readonly [K inkeyof T]: DeepReadonly<T[K]> }
: T
El error más común
type DeepReadonly<T> = T extendsobject
? { readonly [K inkeyof T]: DeepReadonly<T[K]> }
: T
Sin la guarda para funciones, `() => void` encaja en `extends object` y se mapea. Un tipo función no tiene propiedades enumerables propias, así que el resultado es `{}` — se pierde la firma invocable.
Línea por línea
T extends (...args: never[]) => unknown
`never[]` en posición de parámetro acepta cualquier firma porque los parámetros se comprueban de forma contravariante. Es la manera segura de decir «cualquier función» sin recurrir a `any`.
{ readonly [K in keyof T]: DeepReadonly<T[K]> }
El modificador `readonly` se añade durante el mapeo y el tipo del valor recurre. Un solo tipo mapeado hace ambas cosas.
Para recordar
Las ramas condicionales se prueban en orden, así que los casos más estrechos van primero. `extends object` es más amplio de lo que casi todo el mundo espera — arrays y funciones lo cumplen.