typescript - Merkitty tyyppi merkkijonolle ei toimi odotetusti. Ymmärrysvirhe vai bugi?
Miksi S on string seuraavassa koodissa?
type T<X> = {[K in keyof X]: never};
type S = T<string>;
Tässä on TypeScript-leikkikenttä. https://www.typescriptlang.org/play?#code/C4TwDgpgBAKgPADQHxQLxQN4G0DSUCWAdlANYQgD2AZlAgLoBcUhEAbhAE4C+A3AFChIUAMppYcAM7AORAOZIeQA
Se muuttuu samantyyppiseksi (string), vaikka vaihtaisin never:n muihin tyyppeihin.
Lisäämällä kontekstia, seuraava tyyppi toimii odotetusti.
type T<X> = {[K in (keyof X) & string]: never};
type S = T<string>;
Vastaus #1
Periaatteessa mitä teet, on kartoitus tyypin string avaimista, jotka ovat number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" ... jne., asettamalla niiden tyypit never. Voit tehdä sen yksinkertaisella unioneilla.
type X = keyof string; // type X = number | typeof Symbol.iterator | "toString" ...
Mutta ei avaimen kartoituksen tavalla, jota yritit. Se, minkä loit, on niin sanottu homomorfinen kartoitettu tyyppi. Tämä tarkoittaa, että kääntäjä tunnistaa, että yrität kartoittaa jo olemassa olevan objektityypin avaimia. Kun näin on, kääntäjä yksinkertaisesti palauttaa tyypin, jolla on täsmälleen samat ominaisuuksien muokkaajat kuin syöttötyypissäsi.
Lyhyesti sanottuna, kun kartoitetaan primitiivityypin ominaisuuksia, TypeScript palauttaa vain kyseisen primitiivisen tyypin.
Tämä muunnettu tyyppi palauttaa primitiivisen tyypin, ei objektityyppiä. Muunnetut tyypit, jotka on määritelty muodossa
{ [ K in keyof T ]: U }, missäTon tyyppiparametri, tunnetaan homomorfisina muunnettuina tyyppeinä, mikä tarkoittaa, että muunnettu tyyppi onT:n rakennetta säilyttävä funktio. Kun tyyppiparametriTluodaan primitiivisellä tyypillä, muunnettu tyyppi arvioituu samaksi primitiiviseksi tyypiksi. - TypeScript FAQ Virheet, jotka eivät ole virheitä
Vastaus #2
Kiertotapa tietylle määritellylle tyypille
Alla on kiertotapa primitiivityyppien kartoittamiseen:
// doesn't work for primitive types
type MappedTypeNoPrimitive<T> = {
[K in keyof T]: never /* any mapping */
}
// also works for primitive types
// Note the similarity to `Pick<T, keyof T>`
type MappedType<T, Keys extends keyof T> = {
[K in Keys]: never /* any mapping */
}
type MappedString = MappedType<string, keyof string>
/*
type MappedString = {
readonly [x: number]: never;
[Symbol.iterator]: never;
toString: never;
charAt: never;
charCodeAt: never;
concat: never;
indexOf: never;
lastIndexOf: never;
localeCompare: never;
match: never;
replace: never;
... 39 more ...;
at: never;
}
*/
Selitys: Näyttää siltä, että [K in keyof T]-osa MappedTypeNoPrimitive-tyypissä tunnistaa primitiiviset T-tyypit, esim. [K in keyof string], ja palauttaa string, ohittaen mapin. Keys-tyypin käyttäminen MappedType-tyypissä näyttää välttävän tämän.
Yleinen kiertotapa käyttäen mukautettua KeyOf<T>
Jos haluat kartoittaa primitiivisiä tyyppejä useissa paikoissa, voit käyttää
// converts any type (objects, primitives, functions) to an object `{...}`
// Notes: I don't know why, but we need `T extends object` such that `KeyOf<T>` works
type ToObject<T> = T extends object ? T : Pick<T, keyof T>
// returns `keyof T` such that primitive types can be mapped
// Notes: We need `& keyof T` such that typescript can use `KeyOf<T>` in a mapped type
type KeyOf<T> = keyof ToObject<T> & keyof T
// Works also with primitive types
type MappedType2<T> = {
[K in KeyOf<T>]: never /* any mapping */
}
