Wzorce
Dlaczego `Object.keys(config)` daje mi `string[]` zamiast kluczy, które widzę na miejscu?
Bo wartość może mieć więcej kluczy, niż deklaruje jej typ, a `Object.keys` zwraca wszystkie. Zawężenie wyniku to świadoma, lokalna decyzja — a nie naprawa usterki.
Przepis
type Keys<T extends object> = (keyof T)[] const typedKeys = <T extends object>(value: T): Keys<T> => Object.keys(value) as Keys<T> const config = { retries: 3, verbose: true } const keys = typedKeys(config)
Build to kompiluje i sprawdza każdy wynik poniżej.
Jak to działa
- 01
type Keys<T extends object> = (keyof T)[]
Kształt, jaki chciałbyś, by miało `Object.keys`. Osobna nazwa trzyma rzutowanie w jednym miejscu i utrzymuje czytelność.
- 02
Object.keys(value) as Keys<T>Rzutowanie, i to uczciwe: twierdzisz, że obiekt nie ma kluczy poza swoim typem. Dla literału, który właśnie zbudowałeś, to prawda; dla czegoś, co przyszło skądinąd, może nie być.
Co dostajesz
typeof keys→("retries" | "verbose")[]Keys<{ a: 1; b: 2 }>→("a" | "b")[](typeof keys)[number]
→"retries" | "verbose"A indeksowanie przez `number` odzyskuje unię — zwykle to po niej chciałeś iterować.
Gdzie to zawodzi
Rzutowanie jest kłamstwem zawsze, gdy obiekt przyszedł spoza twojego programu. Nadmiarowe właściwości są legalne — wartość o typie `{ a: 1 }` może w czasie wykonania nieść `b` — a ten pomocnik poda ci listę kluczy bez niej, twierdząc, że jest kompletna.
Wniosek
`Object.keys` zwracające `string[]` to nie błąd projektowy; to system typów uczciwy wobec nadmiarowych właściwości. Używaj tego pomocnika na obiektach, które zbudowałeś, a nie na tych, które otrzymałeś.