TypeScript: איך `ליישם` על מבנה שמכיל רשומה?
נניח שיש לנו מבנה שמחזיק רשומה שעליה אנו רוצים להחיל מבנה אחר שמחזיק רשומה שמחזיקה פונקציות עבור המפתחות של המבנה הראשון. (בעצם ההיפך מה-Apply לפי מפרט פנטזי לנד)
בואו קודם נגדיר כמה סוגים שבהם נשתמש:
type Index = string|number|symbol
type Struct<R extends Record<Index, any>> = {
readonly value: R
apOn: <B>(fab: Struct<{[K in keyof R]?: (a: R[K], k: K) => B}>) =>
Struct<{[K in keyof R]: B}>
}
אז מתמודד עם המבנה עצמו
declare function newStruct<R extends Record<Index, any>> (value: R): Struct<R>
const struct = newStruct ({a: 1, b: true})
שני הטעונים הראשונים פועלים כמצופה כאשר מציינים עבור פרמטר המתודות apOn מבנה Struct עם אותו מספר מפתחות אשר מכילים פונקציות עם סוג החזרה זהה.
const withFuncsa = newStruct ({
a: (a: number) => `${a}`,
b: (a: boolean) => `${a}`,
})
const withFuncsb = newStruct ({
a: (a: number) => a > 0,
})
const resa = struct.apOn (withFuncsa).value // as expected {a: string, b: string}
const resb = struct.apOn (withFuncsb).value // as expected {a: boolean, b: boolean}
אבל נשבר כאשר סוגי ההחזרה שונים:
const withFuncsc = newStruct ({
a: (a: number) => a > 0,
b: (a: boolean) => `${a}`,
})
const resc = struct.apOn (withFuncsc).value
// breaks cause expect all returns type of this withFuncc function to be identical
// expected {a: boolean, b: string}
האם יש דרך לגרום לזה לעבוד עם סוגי החזרה שונים?
תשובה #1
אנחנו צריכים לעבד מחדש את החתימה כדי לדחות את ההחלטה על B עד שנודע המפתח שהוא מחשב עבורו.
type Struct<R extends Record<Index, any>> = {
readonly value: R
apOn: <F extends {[K in keyof Partial<R>]: (a: R[K], k: K) => any}>
(fab: Struct<F>) =>
Struct<Omit<R, keyof F> & {[K in keyof F]: ReturnType<F[K]>}>
}
כאן F הוא הרשומה של הממפים. חשוב ש-[K in keyof Partial<R>]: ... יהיה במקום [K in keyof R]?: ... מכיוון שאנחנו רוצים לוודא שהסמן האופציונלי לא יתשטח ל-... | undefined. הממפים קיימים והם פונקציה או לא קיימים.
אז בסוג ההחזרה נשאיר את השדות שלא מוּיפוּים כפי שהם Omit<R, keyof F> אך עבור כל השדות המוּיפוּיים, נאמץ את הסוג שמיוצר על ידי הממפה, {[K in keyof F]: ReturnType<F[K]>}.
הסוגים הביניים נראים קצת מבולגנים ב-IntelliSense אבל הם מבצעים את העבודה:
const t: {strings: string[], bools: boolean[]} = {
strings: [resa.a, resa.b, resc.b],
bools: [resb.a, resb.a, resc.a],
}
קישור למגרש משחקים
