Vyhledávání na webu

typescript - Mapovaný typ pro řetězec nefunguje podle očekávání. Nedorozumění nebo chyba?


Proč je S string v následujícím kódu?

type T<X> = {[K in keyof X]: never};
type S = T<string>;

Zde je TypeScript playground. https://www.typescriptlang.org/play?#code/C4TwDgpgBAKgPADQHxQLxQN4G0DSUCWAdlANYQgD2AZlAgLoBcUhEAbhAE4C+A3AFChIUAMppYcAM7AORAOZIeQA

Stává se to stejným typem (string), i když never nahradím jinými typy.

Přidáním kontextu funguje následující typ tak, jak očekávám.

type T<X> = {[K in (keyof X) & string]: never};
type S = T<string>;

Odpověď č. 1

V podstatě to, co děláte, je mapování klíčů typu string, kterými jsou number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" ... atd., a nastavujete jejich typy na never. To můžete udělat pomocí jednoduché unie.

type X = keyof string; // type X = number | typeof Symbol.iterator | "toString" ...

Ale ne pomocí mapování klíčů způsobem, který jste zkoušel. To, co jste vytvořil, je takzvaný homomorfně mapovaný typ. To znamená, že kompilátor rozpozná, že se snažíte namapovat klíče již existujícího typu objektu. V takovém případě kompilátor jednoduše vrátí typ se stejnými modifikátory vlastností jako u vašeho vstupního typu.

Stručně řečeno, při mapování vlastností primitivního typu TypeScript jednoduše vrátí právě tento primitivní typ.

Tento mapovaný typ vrací primitivní typ, nikoli typ objektu. Mapované typy deklarované jako { [ K in keyof T ]: U }, kde T je typový parametr, jsou známé jako homomorfní mapované typy, což znamená, že mapovaný typ je strukturálně zachovávající funkce typu T. Když je typový parametr T instanciován primitivním typem, mapovaný typ se vyhodnotí na stejný primitivní typ. - Často kladené otázky TypeScriptu: Chyby, které nejsou chybami

Odpověď č. 2

Obejití pro konkrétní mapovaný typ

Níže je řešení pro mapování primitivních typů:

// 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;
}
*/

Vysvětlení: Zdá se, že část [K in keyof T] v MappedTypeNoPrimitive rozpoznává primitivní typy T, např. [K in keyof string] a vrací string, ignoruje mapování. Použití Keys v MappedType se zdá tomu předcházet.

Obecné obcházení pomocí vlastního KeyOf<T>

Pokud chcete mapovat primitivní typy na více místech, můžete použít

// 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 */
}