Búsqueda de sitios web

Typescript. Filtrando propiedades funcionales y `No se puede invocar una expresión cuyo tipo carece de una firma de llamada.`


Estoy implementando una función call que recibe un objeto y el nombre de una propiedad y luego invoca la función que reside en el objeto pasado en la propiedad pasada.

function call<
    T,
    K extends {
        [K in keyof T]: T[K] extends Function ? K : never
    }[keyof T],
    >(t: T, func: K) {

    t[func]();
}

Parece que ts está contento con ello y filtra correctamente las propiedades (no prop, solo do disponible):

Pero también muestra un error en la línea donde invoco la función: t[func]();:

Cannot invoke an expression whose type lacks a call signature.
Type '{}' has no compatible call signatures.

He encontrado un problema similar [Determinación de tipo usando tipos mapeados y genéricos], sin embargo, está cerrado como duplicado de otro problema [Firmas de llamada de tipos unión]. Aparentemente, el problema es que el segundo parámetro genérico es en realidad un tipo unión de todos los nombres de propiedades que cumplen la condición.

¿Hay otras formas de arreglar esto excepto hacer un casting a any?

PD: Patio de recreo

Respuesta #1

Typescript no podrá determinar que tu filtrado de las propiedades significará que T[K] siempre es una función. Puedes invertir un poco tu restricción, para especificar que T extiende algo que tiene propiedades de tipo ()=>void

function call<
    T extends { [P in K]: ()=> void },
    K extends keyof T
    >(t: T, func: K) {

    t[func]();
}

class Test {
    prop: string = "";
    doStuff(): void {}
}

call(new Test(), "prop") // error
call(new Test(), "doStuff") // ok

Sin embargo, podrías considerar usar una aserción en tu función original, la versión anterior, aunque evita las aserciones y es segura en cuanto a tipos, no ayuda a IntelliSense a determinar qué valores posibles se pueden asignar al segundo parámetro, así que en lugar de esto:

Tú consigues esto:

Aunque generalmente se deben evitar las aserciones, en este caso estamos seguros de que el tipo será realmente correcto porque tenemos restricciones en la clave, así que esta versión también debería estar bien:

function call2<
T,
K extends {
    [K in keyof T]: T[K] extends ()=> void ? K : never
}[keyof T],
>(t: T, func: K) {

    (t[func] as any as ()=> void)();
}