la stringa non può essere utilizzata per indicizzare il tipo "T"
Ho attivato la modalità rigorosa in TypeScript e ho problemi con l'impostazione di un indice su un oggetto anche se ho impostato un indicizzatore di stringhe sul suo tipo.
La disabilitazione di noImplicitAny lo risolve, ma preferirei di no.
Ho studiato attentamente questa risposta ma non ho avuto successo con i suggerimenti.
type stringKeyed = { [key: string]: any }
type dog = stringKeyed & { name: string }
type cat = stringKeyed & { lives: number }
function setLayerProps<T extends dog | cat>(
item: T,
props: Partial<T>
) {
if (item) {
Object.entries(props).forEach(([k, v]) => {
item[k] = v; // Error: Type 'string' cannot be used to index type 'T'.ts(2536)
});
}
}
let d = { name: 'fido' } as dog;
let c = { lives: 9 } as cat;
setLayerProps(d, { name: 'jim' })
setLayerProps(c, { lives: --c.lives })
Qualcuno può vedere cosa sto facendo di sbagliato?
Grazie!
Risposte
Questo è un effetto di # 30769 e un cambiamento di rottura noto. In precedenza abbiamo consentito cose folli come questa senza errori:
function foo<T extends Record<string, any>>(obj: T) {
obj["s"] = 123;
obj["n"] = "hello";
}
let z = { n: 1, s: "abc" };
foo(z);
foo([1, 2, 3]);
foo(new Error("wat"));
In generale, il vincolo Record <stringa, XXX> non garantisce effettivamente che un argomento abbia una firma di indice di stringa, ma si limita a garantire che le proprietà dell'argomento siano assegnabili al tipo XXX. Quindi, nell'esempio sopra puoi passare efficacemente qualsiasi oggetto e la funzione potrebbe scrivere su qualsiasi proprietà senza alcun controllo.
Nella 3.5 imponiamo che puoi scrivere su un elemento di firma di indice solo quando sappiamo che l'oggetto in questione ha una firma di indice. Quindi, hai bisogno di una piccola modifica alla funzione clone:
function clone<T extends Record<string, any>>(obj: T): T {
const objectClone = {} as Record<string, any>;
for (const prop of Reflect.ownKeys(obj).filter(isString)) {
objectClone[prop] = obj[prop];
}
return objectClone as T;
}
Con questa modifica ora sappiamo che objectClone ha una firma di indice di stringa.
Quindi il tuo codice dovrebbe essere
function setLayerProps<T extends dog | cat>(
item: T,
props: Partial<T>
) {
if (item) {
Object.entries(props).forEach(([k, v]) => {
(item as dog | cat)[k] = v;
});
}
}
Riferimento: https://github.com/microsoft/TypeScript/issues/31661#issuecomment-497138929
Disclaimer: questa non è proprio una risposta ma il risultato del mio girovagare su questo argomento. Per motivi di chiarezza, sto scrivendo una risposta.
TL; DR: il tipo di chiavi oggetto è string | number, nonstring
Farlo funzionare
Ho usato il playground TypeScript e ho finito con qualcosa che funziona con TypeScript 4.0.2:
type stringKeyed = { [key: string]: any };
type dog = stringKeyed & { name: string };
type cat = stringKeyed & { lives: number };
function setLayerProps<T extends dog | cat>(item: T, props: Partial<T>) {
if (item) {
Object.entries(props).forEach(([k, v]) => {
// With an explicit key casting, TypeScript does not complain
const key = k as keyof T;
item[key] = v;
});
}
}
let d = { name: "fido" } as dog;
let c = { lives: 9 } as cat;
setLayerProps(d, { name: "jim" });
setLayerProps(c, { lives: --c.lives });
console.log(d); // [LOG]: { "name": "jim" }
console.log(c); // [LOG]: { "lives": 8 }
Perché funziona?
Il tipo di intersezione è ... non un incrocio
Secondo la documentazione di TypeScript :
Un tipo di intersezione combina più tipi in uno. Ciò consente di aggiungere insieme tipi esistenti per ottenere un unico tipo con tutte le funzionalità necessarie.
Dannazione, avrei voluto prestare più attenzione a quelle due righe prima.
Sono stato ingannato dal testo di intersezione e ho pensato che dogtipo avrebbe consentito un solo tasto ( name), ma non è questo il caso. Secondo i tipi di intersezione nell'articolo TypeScript in codingblast , il tipo di intersezione fa sì che il tipo figlio erediti tutte le proprietà dai tipi intersecati.
In altre parole:
type stringKeyed = { [key: string]: any };
type dog = stringKeyed & { name: string };
significa che
- Per la
namechiave è previsto un valore stringa - Per qualsiasi altra chiave, è consentito qualsiasi valore
La seguente dichiarazione è quindi corretta:
let d: dog = {name: 'fido' };
d.something = else;
d.age = 3;
In questa fase, l'unica conclusione che siamo tentati di trarre è che tutte le dogchiavi sono solo stringhe . Beh ... non esattamente.
JavaScript ... non conosce la differenza tra una chiave numerica e una chiave stringa T_T
Per citare questa eccellente risposta di StackOverflow :
Come definito nelle Note di rilascio di TypeScript 2.9, se si digita un'interfaccia con una firma di indice di stringa restituisce un'unione di stringa e numero
E la documentazione di TypeScript menziona:
Se il tipo ha una stringa o una firma di indice numerico, keyof restituirà invece quei tipi:
type Mapish = { [k: string]: boolean }; type M = keyof Mapish; // ^ = type M = string | number
Per convincermi, ho provato quanto segue:
type keys = keyof stringKeyed;
type dogKeys = keyof dog;
const keyAsStr: dogKeys = "this is OK";
const keyAsNum: dogKeys = 42;
Entrambi keyse dogKeysrisolverlo string | number.
stringa! == stringa | numero
Ora il motivo per cui l'errore Error: Type 'string' non può essere utilizzato per indicizzare il tipo 'T'.ts (2536) è semplicemente perché un tipo di chiave non può mai essere stringsolo. La versione "minima" è string | number, da qui l'errore di battitura. È interessante notare che, quando si tenta di ottenere il Ttipo di chiave in setLayerProps, passando il mouse sul tipo non viene visualizzato immediatamente il tipo. Quindi ho provato con
// Inside the setLayerProps function
type tKey = keyof T;
const asStr: tKey = "43";
const asNum: tKey = 43;
const asObj: tKey = { a: "a" };
E ho finito con il seguente errore:
Type '{ a: string; }' is not assignable to type 'keyof T'.
Type '{ a: string; }' is not assignable to type 'string | number'.
Type '{ a: string; }' is not assignable to type 'number'.
Quindi TypeScript si aspettava davvero un filestring | number .
Per qualche motivo non ho ancora capito, questo non funziona:
const key = k as string | number;
Poiché porta a errori:
Type 'number' cannot be used to index type 'T'.(2536)
Type 'string' cannot be used to index type 'T'.(2536)
Perché il cast esplicito è sicuro
Nella setLayerPropsfunzione, sappiamo che la itemchiave è una stringo una number. Quindi casting esplicito
const key = k as keyof T;
significa che sto dicendo che una stringa è una stringa o un numero , il che, a mio avviso, è sempre vero.