typescript - Typsignatur för en metod som vidarebefordrar anrop till en samling av åtgärder


I vårt bibliotek har vi en samling fördefinierade åtgärder som kan köras.

Istället för att exponera dessa åtgärder som en del av API:et, ska en enda metod exponeras för att kalla åtgärden, eftersom vi behöver köra några andra oberoende åtgärder vid sidan av själva åtgärden (t.ex. logga de utförda åtgärderna). Men jag sitter för närvarande fast i implementeringen av själva åtgärdsanropet. Nedan är det bästa jag för tillfället kunde göra. Typescript kan korrekt föreslå argumenten baserat på det passerade handlerName till callHandler, men det misslyckas vid spridningsparametern och klagar på att argumentet inte är en tupel. Men typen Parameters returnerar en tupel, om jag förstår det korrekt.

Se koden nedan eller i TypeScript-playgrounden. Tack på förhand för alla tips!

/**
 * We have a predefined set of functions, with a fixed call signature.
 * This is predefined in the Handlers type:
 */
type Handlers = {
    add: {
        handler: (argument: string) => string;
    };
    count: {
        handler: (argument: number) => string;
    }
};

const myHandlers : Handlers = {
    add: {
        handler: (argument: string) => `Add ${argument}`,
    },
    count: {
        handler: (argument: number) => `I count to ${argument}`,
    }
};

/**
 * Now, we want to be able, to define a method, that proxies the calls to these functions (to do other checks upfront).
 * How can this be typed safely?
 */
function callHandler<H extends keyof Handlers>(name: H, ...params: Parameters<Handlers[H]["handler"]>) {
    return myHandlers[name].handler(...params);
}


// Example call:
console.log(callHandler("count", 10));

Svar #1

Jag kunde få det att fungera så här, först typdeklarationen som du hade tidigare med några tillägg ;) (observera att du kan lägga till fler argumenttyper i arrayen på detta sätt [number, string, ...] och sedan kan du också dela upp dem i implementationerna eller bara ta dem en efter en som handler: (arg_0: type0, arg_1: type1...)=>{}):

type Handlers = {
     add: {
        handler: (...args: [string]) => string;
     };
     count: {
        handler: (...args: [number]) => string;
     };
};

nu behöver du skapa begränsningen för att se till att du inte får åtkomst till en egenskap som inte finns i objektet (i det här fallet behöver du först se till att add eller count finns inne i typen Handlers). Som dokumentationen föreslog.

export function callHandler<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key];
}

detta garanterar att du endast kan använda keys inuti objektet du skickar in i callHandler-funktionen i den första parametern obj, nu för att anropa funktionen gör du det så här:

const myHandlers: Handlers = {
    add: {
        handler: (argument: string) => `Add ${argument}`,
    },
    count: {
        handler: (argument: number) => `I count to ${argument}`,
    },
};
console.log(callHandler(myHandlers, 'count').handler(10)); //I count to 10
console.log(callHandler(myHandlers, 'add').handler("something")); //Add something
console.log(callHandler(myHandlers, 'count').handler("something")); //Error: Argument of type 'string' is not assignable to parameter of type 'number'.
console.log(callHandler(myHandlers, 'bbbb').handler("10")); //Argument of type '"bbbb"' is not assignable to parameter of type 'keyof Handlers'.

Hoppas detta hjälper! Hör av dig om du har några frågor!