typescript - odvodiť typy pre kľúč ku viacerým mapovacím objektom/typom


Zaujímalo by ma, ako vytvoriť mapovania generických typov.

Ako môžete vidieť na obrázku a/alebo na ihrisku..

Kód „beží“ dobre, ale typy sú nejako divné/zmiešané.

Predstavte si, že existuje ešte ďalšie mapovanie na Predmet, takže by som chcel byť schopný urobiť niečo takéto, samozrejme typovo bezpečne, .. :

subjMap[key].next(accessorMap[key]());

Je to vôbec možné s rôznymi objektmi/mapami.. Alebo potrebujem vytvoriť jeden objekt na každé "kľúč", tak povediac..?

type KeyToTypeMapping = {
    iaModel: string;
    debug: boolean;
};

type MappingKeys = keyof KeyToTypeMapping;

type KeyToTypedAccessor<T> = Record<keyof T, () => T[keyof T]>;

const accessorMap: KeyToTypedAccessor<KeyToTypeMapping> = {
    iaModel: () => "string",
    debug: () => true
};

const stringResult = accessorMap.iaModel();

// TS tells me that the type of `stringResult` is string | boolean
console.log(stringResult);

type KeyToTypedFn<T> = {
    [Key in keyof T]: { next: (_: T[Key]) => void };
};

// here is another map with the same keys and "base-types"..
// .. there could be even more ..
const subjMap: KeyToTypedFn<KeyToTypeMapping> = {
    iaModel: { next: (_) => { console.log('next', _); } },
    debug: { next: (_) => { console.log('next', _); } },
}

Object.keys(accessorMap).forEach(k => {
    // sadly we need to cast here..
    const typedKey = k as keyof KeyToTypeMapping;

    const val = accessorMap[typedKey]();

    // here TS complains that val (type of string | boolean) is not assignable to never ..
    subjMap[typedKey].next(val);
});

Odpoveď č. 1

Len nepoužívajte Record, aby ste mohli priamo pristupovať k kľúču!

type KeyToTypeMapping = {
    iaModel: string;
    debug: boolean;
};

type KeyToTypedAccessor<T> = { [P in keyof T]: () => T[P] }

declare const accessorMap: KeyToTypedAccessor<KeyToTypeMapping>

const stringResult = accessorMap.iaModel();

Detské ihrisko

Odpoveď č. 2

Myslím, že hľadáte namapovaný typ, ktorý by vyzeral takto:

type KeyToTypedAccessor<T> = {
    [Key in keyof T]: () => T[Key];
};

Odkaz na ihrisko

Úplný príklad:

type KeyToTypeMapping = {
    iaModel: string;
    debug: boolean;
};

type KeyToTypedAccessor<T> = {
    [Key in keyof T]: () => T[Key];
};

const accessorMap: KeyToTypedAccessor<KeyToTypeMapping> = {
    iaModel: () => "string",
    debug: () => true
};

const stringResult = accessorMap.iaModel();
//    ^? −−−− string
const booleanResult = accessorMap.debug();
//    ^? −−−− boolean

console.log(stringResult);

V komentári ste povedali, že ste uviazli na tomto mieste:

Object.keys(accessorMap).forEach(k => {
    // sadly we need to cast here..
    const typedKey = k as keyof KeyToTypeMapping;

    const val = accessorMap[typedKey]();

    // here TS complains that val (type of string | boolean) is not assignable to never ..
    subjMap[typedKey].next(val);
});

Bohužiaľ, to nemôžete opraviť typovo bezpečným spôsobom bez toho, aby ste niečo nespravili tak, aby TypeScript vedel buď:

  1. Ktorý kľúč z KeyToTypeMapping je typedKey, aby vedel, aký typ hodnoty je. Môže to byť buď (iaModel vedúci k hodnote typu string, alebo debug vedúci k hodnote typu boolean).

    alebo

  2. Že rovnaký kľúč KeyToTypeMapping sa používa tak pre indexovanie do subjMap, ako aj do accessorMap.

#1 vedie k dosť škaredému opakujúcemu sa kódu, takže to nechceme (v tomto prípade).

Čo sa týka #2, mohli by ste si myslieť, že TypeScript by z vášho kódu (alebo ešte zjavnejšie z subjMap[typedKey].next(accessorMap[typedKey]();) vedel, že kľúč je rovnaký, ale chce generickú funkciu, aby mu pomohla:

function setNext<Key extends keyof KeyToTypeMapping>(
    subjMap: KeyToTypedFn<KeyToTypeMapping>,
    accessorMap: KeyToTypedAccessor<KeyToTypeMapping>,
    key: Key,
) {
    subjMap[key].next(accessorMap[key]());
}

Potom môžeme urobiť:

Object.keys(accessorMap).forEach((key) => {
    // (As you say, you're stuck with the type assertion on `key`.)
    setNext(subjMap, accessorMap, key as any as keyof KeyToTypeMapping);
});

Odkaz na ihrisko


(Poznámka: Typové tvrdenia nie sú konverzie. Konverzia [v jazykoch, ktoré ich majú] môže skutočne previesť hodnotu z jednej formy na inú, napríklad prevod dvojkového doplnku int bitového vzoru na IEEE-754 double-precision plávajúci bod double bitový vzor s ekvivalentnou alebo takmer ekvivalentnou číselnou hodnotou, aj keď bitové vzory pre [napríklad] 42 sú medzi týmito dvoma formátmi veľmi odlišné. Typové tvrdenia v TypeScript nikdy nevykonávajú žiadnu konverziu. Dokonca ani neexistujú pri behu programu. Takže sú to len tvrdenia, jednoducho hovoríte TypeScriptu „dôveruj mi, toto ? je X.“ :-) )