typescript - Hur man generellt typifierar ett modifierat ordbok av inkonsekventa funktioner


Jag har en funktion som behöver konvertera ett ordbok med funktioner med delvis dynamiska egenskaper till ett nytt ordbok med funktioner med liknande egenskaper.

Vad jag har hittills:

interface Dictionary<T extends any> {
    [key: string]: T
}

type InputFunction<T> = (value: T, parameters?: any) => T

type InputFunctionMap<T> = Dictionary<InputFunction<T>>

type OutputFunction = (parameters?: any) => void

type OutputFunctionMap<T, U extends InputFunctionMap<T>> = {
    [P in keyof U]: OutputFunction
}

function create<T>(key: string, value: T): [() => T, (value: T) => void]
function create<T, U = InputFunctionMap<T>>(key: string, value: T, inputs: U): [() => T, Readonly<OutputFunctionMap<T, U>>]
function create<T, U = InputFunctionMap<T>>(key: string, value: T, inputs?: U): [() => T, (value: T) => void] | [() => T, Readonly<OutputFunctionMap<T, U>>] {
    // ...
}

Vad jag försöker åstadkomma:

const [getFoo, updateFoo] = create('foo', 'FOO')

getFoo() // => 'FOO'

updateFoo('foo') // => 'foo'

getFoo() // => 'foo'

interface MathParameters {
    operation: 'MULTIPLY' | 'DIVIDE'
    operand: number
}

const [getBar, {addToBar, addSevenToBar, applyMathToBar}] = create('bar', 0, {
    addToBar: (value, parameters: number) => value + parameters,
    addSevenToBar: (value) => value + 7,
    applyMathToBar: (value, parameters: MathParameters) => {
        switch (parameters.operation) {
            case 'MULTIPLY':
                return value * parameters.operand

            default:
                return value
        }
    }
})

getBar() // => 0

addSevenToBar()

getBar() // => 7

addToBar(3)

getBar() // => 10

applyMathToBar({operation: 'MULTIPLY', operand: 2})

getBar() // => 20

Problemet är att jag inte kan lista ut hur man korrekt och generiskt typar parameters (jag vill inte att de ska vara any) så att typen kan härledas från ingångsfunktionen och vara korrekt tillgänglig som parametern i utgångsfunktionen.

Uppdatering:

Tack för stödet hittills!

Jag har bara problem med att få utdata mappningen att fungera som avsett: Typescript Playground

type TailParams<T> = T extends (arg: any, ...args: infer A) => void
    ? A
    : never

const input = inputs[name as keyof U]

outputs[name as keyof U] = (...args: TailParams<typeof input>) => {
    //
}

Kastar: Typen '(...args: TailParams<U[keyof U]>) => void' kan inte tilldelas typen 'TailFunc<U[keyof U]>'.

Svar #1

Jag skulle förmodligen skriva create() så här:

type TailFunc<T> = T extends (h: any, ...args: infer A) => infer R ? (...args: A) => R : never;

declare function create<T>(key: string, value: T): [() => T, (value: T) => void]
declare function create<T, U extends Record<keyof U, (value: T, ...args: any) => any>>(
    key: string,
    value: T,
    inputs: U
): [() => T, { [K in keyof U]: TailFunc<U[K]> }]

Typen TailFunc<T> tar en funktionstyp T och returnerar en ny funktion där den första parametern har tagits bort. Så TailFunc<(a: string, b: number, c: boolean) => string> blir (b: number, c: boolean) => string.

Den andra anropssignaturen för create() är generisk i T som vanligt, och U, vilket är typen för inputs-parametern. Detta är begränsat till Record<keyof U, (value: T, ...args: any)=>any, så det förväntar sig att U är en objekttyp vars värden alla är funktioner vars första parameter är av typen T.

Returtypen för det anropssignaturen är en tuppel där det andra elementet är den mappade typen som utför TailFunc på alla egenskaper i U.

Låt oss se till att det fungerar:

getBar() 
addSevenToBar()
getBar() 
addToBar(3)
getBar() 
applyMathToBar({ operation: 'MULTIPLY', operand: 2 })
getBar() 

De ger alla inga fel i typsystemet, så typerna är vad du vill. Om det beter sig som du förväntar dig vid körning beror på implementeringen av create(), antar jag.

Okej, hoppas det hjälper; lycka till!

Länk till kod

notera att hur man implementerar create() så att allt typkontrollerar med minsta möjliga typantaganden ligger utanför omfattningen av den ursprungliga frågan, så jag lägger bara upp en länk till ett möjligt sätt att göra det: länk till kod

Relaterade artiklar