TypeScript. סינון תכונות פונקציונליות ו-'לא ניתן לקרוא לביטוי שסוגו חסר חתימת קריאה.'


אני מיישם פונקציה call שמקבלת אובייקט ושם של נכס ואז מפעילה את הפונקציה שנמצאת באובייקט שהועבר בנכס שהועבר.

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

    t[func]();
}

נראה ש-ts מרוצה מזה ומסנן נכסים בצורה נכונה (אין prop, יש רק do):

אבל זה גם מציג שגיאה בשורה שבה אני מפעיל את הפונקציה: t[func]();:

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

מצאתי בעיה דומה [Type deduction using mapped types and generics] אך היא נסגרה כתוספת של בעיה אחרת [Call signatures of union types]. ככל הנראה הבעיה היא שהפרמטר הגנרי השני הוא למעשה סוג איחוד של כל שמות המאפיינים שמספקים את התנאי.

האם יש דרכים אחרות לתקן זאת חוץ מההמרה ל-any?

נ.ב: מגרש משחקים

תשובה #1

טייפסקריפט לא יוכל להבין שהסינון שלך של התכונות אומר ש-T[K] תמיד יהיה פונקציה. אתה יכול להפוך מעט את ההגבלה שלך, כדי לציין ש-T מרחיב משהו שיש לו תכונות מסוג ()=>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

ראוי לציין, עם זאת, שאולי כדאי לשקול שימוש בהצהרה (assertion) בפונקציה המקורית שלך, הגרסה למעלה, למרות שהיא נמנעת מהצהרות ובטוחה מבחינת טיפוסים, אינה עוזרת ל-IntelliSense להבין אילו ערכים אפשריים ניתן להקצות לפרמטר השני, אז במקום זאת:

אתה מקבל את זה:

בעוד שכשמחים להימנע מהצהרות בדרך כלל, במקרה זה אנו בטוחים שהסוג יהיה נכון בפועל כי יש לנו מגבלות על המפתח, כך שגרסה זו אמורה להיות בסדר גם כן:

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)();
}