類中的泛型類型

我試圖將泛型類型實現到類中,但事實證明,我不了解泛型可以做什么,不能做什么。

希望有人能幫我!

我想要什么

我已經創建了一個類,該類應該注冊任務并稍后按id運行它們。taskObjecttaskId: string和回調函數callback: <R>(...args: any) => R組成。實例化類時將注冊任務。

其想法是,我有關于每個單獨任務的回調函數返回類型的類型信息。

因此,taskId是一個文字類型,callback應該是一個任意函數,我想推斷它的返回類型,并使這個返回類型在我的類中可訪問。

The code


type Task<T extends string, V extends <R>(...args: any) => R> = {
  taskId: T
  callback: V // <-- I would like this resolved
}

class Taskmanager<T extends string, V extends <R>(...args: any) => R> {

  private callbacks = new Map<T, V>();

  public constructor(tasks: Task<T,V>[]) {
    tasks.forEach(task => {
      this.callbacks.set(task.taskId, task.callback);
    });
  }

  public runCallback(taskId: T) {
    return this.callbacks.get(taskId)?();
  }
}

這是第一步→ 我有所有注冊taskIds的類型提示。

const tm = new Taskmanager([
  {
    taskId: "hello",
    callback: () => "world" // <-- TS error: Type 'string' is not assignable to type 'R'
  },
  {
    taskId: "circle",
    callback: () => 360 // <-- TS error: Type 'number' is not assignable to type 'R'
  }
]);

// I'm now getting type hints for `taskId` → "hello" | "circle"
const resHello = tm.runCallback("hello"); // <-- I would like: typeof resHello === "string" --> true
const resCircle = tm.runCallback("hello"); // <-- I would like: typeof resCircle === "number" --> true

這是TS游樂場鏈接

這給了我預期的結果,即我只能在hellocircle之間選擇作為runCallback中的參數。但是,在類構造函數中聲明回調函數時,我會遇到TS錯誤。

// TS Error for taskId: "hello"
Type 'string' is not assignable to type 'R'.
  'R' could be instantiated with an arbitrary type which could be unrelated to 'string'.

有沒有一種方法可以推斷每個回調函數的返回類型?這可能嗎?

我確實在StackOverflow上查看了其他幾個問題和答案,但不幸的是,到目前為止,我無法提取任何有用的內容。

如果這個問題很愚蠢,我深表歉意。TS有時真的讓我抓狂。。。

非常感謝。🙌

? 最佳回答:

由于您計劃在完全沒有參數的情況下調用這些回調,因此您希望將V保持為() => any,而不是<R>(...args: any) => R(這是通用的,甚至不可能正確實現)。事實上,您甚至可能只想使用R,然后使callback() => R類型,但我將進行最小的更改:

type Task<K extends string, V extends () => any> = {
  taskId: K
  callback: V
}

現在,由于您計劃向Taskmanager構造函數傳遞一個條目數組,我們需要使該類在該數組的元素的類型T中具有泛型,如下所示:

class Taskmanager<const T extends Task<string, () => any>> {

  private callbacks = new Map<string, () => any>;

  public constructor(tasks: readonly T[]) {
    tasks.forEach(task => {
      this.callbacks.set(task.taskId, task.callback);
    });
  }

請注意,我將其設為const類型參數,因為我們希望編譯器推斷taskId屬性的字符串文字類型,而不僅僅是string

還請注意,我將callbacks設為Map<string, ()=>any>,而不是更具體的T。這是因為Map<K, V>沒有跟蹤關系特定鍵和特定值所需的類型。Map<K, V>就像一個字典,其中所有值都是V類型。如果您在Map中需要其他東西,您可以嘗試為其編寫自己的類型,如Typescript:我如何在ES6映射中基于對象鍵/值類型進行條目,但這可能有些過頭了。我們將使用() => any,然后在實現runCallback()時要小心。

它看起來是這樣的:

  public runCallback<K extends T["taskId"]>(taskId: K):
    ReturnType<Extract<T, { taskId: K }>["callback"]> {
    return this.callbacks.get(taskId)!();
  }
}

這是一個相當復雜的調用簽名,但有必要這樣做,因為TTask<string, ()=>any>的子類型的并集。因此,我們希望將K約束到TtaskId屬性的并集,然后返回類型要求我們從并集中Extract獲取其taskId屬性為K類型的成員,獲取其{callback屬性,并獲取其中的ReturnType。實現需要使用non-null斷言運算符來說服編譯器get()不會返回undefined。我們只需要小心,因為編譯器將返回類型視為any,它可以分配給任何對象。我們本可以寫this.callbacks.get(taskId+"oops")!();,它也會編譯。


無論如何,讓我們測試一下:

const tm = new Taskmanager([
  {
    taskId: "hello",
    callback: () => "world"
  },
  {
    taskId: "circle",
    callback: () => 360
  }
]);
/* const tm: Taskmanager<{
    readonly taskId: "hello";
    readonly callback: () => string;
} | {
    readonly taskId: "circle";
    readonly callback: () => number;
}> */

const res = tm.runCallback("hello");
// const res: string
console.log(res.toUpperCase()) // "WORLD"

看起來不錯。構造函數調用被接受,并且tm的類型編碼了足夠的關于構造函數參數的信息,以計算tm.runCallback("hello")的正確類型,即string

游樂場代碼鏈接

主站蜘蛛池模板: 日韩精品无码一区二区三区不卡| 亚洲一区爱区精品无码| 中文字幕乱码一区二区免费| 亚洲av日韩综合一区在线观看| 日本免费一区二区在线观看| 亚洲毛片αv无线播放一区| 亚洲一本一道一区二区三区| 久久久久成人精品一区二区 | 日韩中文字幕精品免费一区| 日韩免费一区二区三区在线| 日韩久久精品一区二区三区 | 人妻在线无码一区二区三区| 国产人妖在线观看一区二区| 一区二区三区无码高清视频| 成人影片一区免费观看| 无码国产伦一区二区三区视频 | 国产精品美女一区二区| 一区二区在线视频免费观看| 国产成人av一区二区三区在线| 竹菊影视欧美日韩一区二区三区四区五区| 久久精品无码一区二区三区| 国模吧无码一区二区三区| 久久精品一区二区影院 | 波多野结衣AV一区二区三区中文| 少妇激情AV一区二区三区| 人妻无码一区二区三区AV| 亚洲乱色熟女一区二区三区蜜臀| 少妇激情一区二区三区视频| 日本午夜精品一区二区三区电影| 91成人爽a毛片一区二区| 91video国产一区| 无码精品一区二区三区| 色综合久久一区二区三区| 国产女人乱人伦精品一区二区| 国产一区二区三区手机在线观看| 亚洲性日韩精品一区二区三区 | 人妖在线精品一区二区三区| 国产精品电影一区| 日韩精品一区二区三区中文版| 国产日韩综合一区二区性色AV| 国产精品亚洲高清一区二区|