TypeScript: Object.keys vraća string[]
Kada koristite Object.keys(obj), povratna vrijednost je string[], dok ja želim (keyof obj)[].
const v = {
a: 1,
b: 2
}
Object.keys(v).reduce((accumulator, current) => {
accumulator.push(v[current]);
return accumulator;
}, []);
Imam pogrešku:
Element implicitno ima tip 'any' jer tip '{ a: number; b: number; }' nema indeksni potpis.
TypeScript 3.1 s strict: true. Igralište: ovdje, molimo označite sve kućice u Opcije kako biste aktivirali strict: true.
Odgovor #1
Object.keys vraća string[]. To je namjerno, kao što je opisano u ovom problemu
Ovo je namjerno. Tipovi u TS su otvoreni. Tako će keysof vjerojatno biti manji od svih svojstava koja biste dobili u runtimeu.
Postoji nekoliko rješenja, najjednostavnije je jednostavno koristiti pretpostavku tipa:
const v = {
a: 1,
b: 2
};
var values = (Object.keys(v) as Array<keyof typeof v>).reduce((accumulator, current) => {
accumulator.push(v[current]);
return accumulator;
}, [] as (typeof v[keyof typeof v])[]);
Također možete stvoriti alias za keys u Object koji će vratiti tip koji želite:
export const v = {
a: 1,
b: 2
};
declare global {
interface ObjectConstructor {
typedKeys<T>(obj: T): Array<keyof T>
}
}
Object.typedKeys = Object.keys as any
var values = Object.typedKeys(v).reduce((accumulator, current) => {
accumulator.push(v[current]);
return accumulator;
}, [] as (typeof v[keyof typeof v])[]);
Odgovor #2
Na temelju odgovora i komentara Titiane Cernicove-Dragomir
Koristite tvrdnju tipa samo ako znate da vaš objekt nema dodatnih svojstava (što je slučaj za literal objekta, ali ne i za parametar objekta).
Izričita tvrdnja
Object.keys(obj) as Array<keyof typeof obj>
Skrivena tvrdnja
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>
Koristite getKeys umjesto Object.keys. getKeys je referenca na Object.keys, ali povratna vrijednost je doslovno tipizirana.
Rasprave
Jedno od temeljnih načela TypeScripta je da provjera tipova fokusira na oblik koji vrijednosti imaju. (referenca)
interface SimpleObject {
a: string
b: string
}
const x = {
a: "article",
b: "bridge",
c: "Camel"
}
x se kvalificira kao SimpleObject jer ima svoj oblik. To znači da kada vidimo SimpleObject, znamo da ima svojstva a i b, ali može imati i dodatna svojstva.
const someFunction = (obj: SimpleObject) => {
Object.keys(obj).forEach((k)=>{
....
})
}
someFunction(x)
Pogledajmo što bi se dogodilo ako bismo prema zadanim postavkama tipkali Object.keys onako kako OP želi "doslovno":
Dobili bismo da je typeof k "a"|"b". Prilikom iteriranja stvarne vrijednosti bi bile a, b, c. Typescript nas štiti od takve pogreške tako što tipizira k kao string.
Asercija tipa je upravo za takve slučajeve - kada programer ima dodatno znanje. Ako znate da obj nema dodatna svojstva, možete koristiti aserciju tipa literala.
Odgovor #3
Potpuno se ne slažem s odlukom tima TypeScripta...
Slijedeći njihovu logiku, Object.values uvijek bi trebao vraćati any, jer bismo mogli dodati više svojstava u vrijeme izvođenja...
Mislim da je ispravan način stvaranje sučelja s opcionalnim svojstvima i postavljanje (ili nepostavljanje) tih svojstava kako ideš...
Dakle, jednostavno sam lokalno prepisao ObjectConstructor sučelje, dodajući deklaracijsku datoteku (tj. whatever.d.ts) u svoj projekt sa sljedećim sadržajem:
declare interface ObjectConstructor extends Omit<ObjectConstructor, 'keys' | 'entries'> {
/**
* Returns the names of the enumerable string properties and methods of an object.
* @param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
keys<O extends any[]>(obj: O): Array<keyof O>;
keys<O extends Record<Readonly<string>, any>>(obj: O): Array<keyof O>;
keys(obj: object): string[];
/**
* Returns an array of key/values of the enumerable properties of an object
* @param obj Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
entries<T extends { [K: Readonly<string>]: any }>(obj: T): Array<[keyof T, T[keyof T]]>
entries<T extends object>(obj: { [s: string]: T } | ArrayLike<T>): [string, T[keyof T]][];
entries<T>(obj: { [s: string]: T } | ArrayLike<T>): [string, T][];
entries(obj: {}): [string, any][];
}
declare var Object: ObjectConstructor;
Napomena :
Object.keys/Object.entries primitivnih tipova (objekta) vratit će never[] i [never, never][] umjesto uobičajenog string[] i [string, any][]. Ako netko zna rješenje, slobodno mi recite u komentarima i ja ću urediti svoj odgovor
const a: {} = {};
const b: object = {};
const c: {x:string, y:number} = { x: '', y: 2 };
// before
Object.keys(a) // string[]
Object.keys(b) // string[]
Object.keys(c) // string[]
Object.entries(a) // [string, unknown][]
Object.entries(b) // [string, any][]
Object.entries(c) // [string, string|number][]
// after
Object.keys(a) // never[]
Object.keys(b) // never[]
Object.keys(c) // ('x'|'y')[]
Object.entries(a) // [never, never][]
Object.entries(b) // [never, never][]
Object.entries(c) // ['x'|'y', string|number][]
Dakle, koristite ovo s oprezom...
Odgovor #4
1. npm install ts-extras (napisao sindresorhus)
- Koristi ga:
import { objectKeys } from 'ts-extras'
objectKeys(yourObject)
To je to.
Evo još jednog paketa koji sam napravio prije nego što sam saznao za ts-extras:
npm install object-typed --save
import { ObjectTyped } from 'object-typed'
ObjectTyped.keys({ a: 'b' })
Ovo će vratiti niz tipa ['a']
