簡體   English   中英

在泛型類型約束中保留特定的 function 簽名 - TypeScript

[英]Keep specific function signatures in a generic type constraint - TypeScript

起初:對不起,很長的帖子,事實上我在這里有兩個不同的問題。 但是由於兩者之間的關系如此密切(實現相同目標的兩種不同方法),我認為最好將它們放在一個帖子中。

我正在嘗試實現一個輕量級 TypeScript 信號/事件框架,其中

  • 所有可能的信號名稱都是已知的並且具有 IntelliSense 支持
  • 它們的簽名(參數和返回值)是強類型的,並且也支持 IntelliSense

我知道存在一些以某種方式實現類似目標的解決方案,但我有一個特定的簡單易用的設計,它利用了較新的 TypeScript 通用索引類型約束功能 - 但現在我顯然無法自己掌握它們:)

我正在使用 TypeScript 4.2.3。

我嘗試了兩種不同的方法,但結果不同......

第一種方法

這可以編譯,但我們在信號 arguments 上沒有任何類型並返回值:

// Define signals by name and a function, which specifies SignalArgs
// and SignalValue (return value).
type Signals = {
  hello: (name: string) => string;
  add: (val1: number, val2: number) => number;
};

// Generic class which should implement on() and emit() with arguments
// bound to the specific signals enumerated in the Signals type.
class SignalBus<Signals> {
  on<Key extends string & keyof Signals, Spec extends (...args: any) => any & Signals[Key]>(
    signal: Key,
    handler: (
      signal: Signal<Parameters<Spec>, ReturnType<Spec>>,
      ...args: Parameters<Spec>
    ) => Promise<ReturnType<Spec>>,
  ): SignalConnection<Parameters<Spec>, ReturnType<Spec>> {
    // ...
    console.log("on", signal, handler);
    const connection = (handler as unknown) as SignalConnection<Parameters<Spec>, ReturnType<Spec>>;
    connection.signal = signal;
    return connection;
  }

  async emit<Key extends string & keyof Signals, Spec extends (...args: any) => any & Signals[Key]>(
    signal: Key,
    ...args: Parameters<Spec>
  ): Promise<Signal<Parameters<Spec>, ReturnType<Spec>>> {
    // ...
    console.log("emit", signal, args);
    return new Signal<Parameters<Spec>, ReturnType<Spec>>(signal, args);
  }
}

// Signal objects represent a signal at runtime
class Signal<SignalArgs extends unknown[] = [], SignalValue = any> {
  public value: SignalValue;
  constructor(public signal: string, public args: SignalArgs) {}
}

// SignalConnection objects enclose the handler callback, signal name and probably more
type SignalConnection<SignalArgs extends unknown[], SignalResult> = {
  (signal: Signal<SignalArgs, SignalResult>, ...args: SignalArgs): Promise<SignalResult | void>;
  signal: string;
};

// Create a SignalBus specific to the Signals defined above
const bus = new SignalBus<Signals>();

// Fine
bus.on("hello", async (_signal, name) => `Hello ${name}`);

// Should give type error, because two numeric arguments and numeric return value are expected
bus.on("add", async (_signal, name: string) => `Shouldn't be accepted ${name}`);

// Should give type error, because only one string argument is expected
bus.emit("hello", "World", "shouldn't be accepted");

問題是特定的 function 簽名,例如在 on() 方法中,會丟失:

on<Key extends string & keyof Signals, Spec extends (...args: any) => any & Signals[Key]>(...)

使用Spec extends (...args: any) => any會發生這種情況。

我知道我必須將Signals[Key]屬性限制為可調用的 function 以使Parameters<>ReturnValue<>實用程序類型工作。

但是如何在不丟失Signals[Key]函數的類型信息的情況下做到這一點?

有什么方法可以實現我在這里嘗試做的事情嗎?

另一種方法,另一個問題;)

在這里,我定義了一個通用signal<>類型,它由信號參數和返回值類型組成,並通過索引訪問它們——但也沒有運氣......

使用 SignalBus class 時,編譯器會抱怨不可分配的never類型。 請參閱代碼示例底部的注釋:

// Generic type to specify signal arguments and return value.
type signal<SignalArgs extends unknown[] = [], SignalResult = void> = {
  args: SignalArgs;
  result: SignalResult;
};

// Define signals by name and the generic signal<> type.
type Signals = {
  hello: signal<[name: string], string>;
  add: signal<[val: number, val2: number], string>;
};

// Generic class which should implement on() and emit() with arguments
// bound to the specific signals enumerated in the Signals type.
class SignalBus<Signals> {
  on<Key extends string & keyof Signals, Spec extends signal & Signals[Key]>(
    signal: Key,
    handler: (
      signal: Signal<Spec["args"], Spec["result"]>,
      ...args: Spec["args"]
    ) => Promise<Spec["result"]>,
  ): SignalConnection<Spec["args"], Spec["result"]> {
    // ...
  }

  async emit<Key extends string & keyof Signals, Spec extends signal & Signals[Key]>(
    signal: Key,
    ...args: Spec["args"]
  ): Promise<Signal<Spec["args"], Spec["result"]>> {
    // ...
  }
}

// Signal objects represent a signal at runtime
class Signal<SignalArgs extends unknown[] = [], SignalValue = any> {
  public value: SignalValue;
  constructor(public signal: string, public args: SignalArgs) {}
}

// SignalConnection objects enclose the handler callback, signal name and probably more
type SignalConnection<SignalArgs extends unknown[], SignalResult> = {
  (signal: Signal<SignalArgs, SignalResult>, ...args: SignalArgs): Promise<SignalResult | void>;
  signal: string;
};

// Create a SignalBus specific to the Signals defined above
const bus = new SignalBus<Signals>();

// Type 'Promise<string>' is not assignable to type 'Promise<never>'.
//   Type 'string' is not assignable to type 'never'. ts(2322)
bus.on("hello", async (_signal, name) => `Hello ${name}`);

// Argument of type '(_signal: Signal<never, never>, name: string) => Promise<string>' is not assignable to parameter of type '(signal: Signal<never, never>, ...args: never) =>
// Promise<never>'.
//   Type 'Promise<string>' is not assignable to type 'Promise<never>'. ts(2345)
bus.on("add", async (_signal, name: string) => `Shouldn't be accepted ${name}`);

// Argument of type 'string' is not assignable to parameter of type 'never'. ts(2345)
bus.emit("hello", "World", "shouldn't be accepted");

它看起來類型約束Spec extends signal & Signals[Key]永遠不匹配。 為什么?

有人對此有任何想法嗎?

由於 generics 之一中的以下擴展子句,該方法不起作用:

Spec extends (...args: any) => any & Signals[Key]

由於示例中的Signals應始終是鍵/功能對的集合,因此最終將評估為如下所示:

Spec extends (...args: any) => any & ((exampleArg: string) => string)

(...args: any) => any & ((exampleArg: string) => string)的交集將始終是(...args: any) => any 這對於任何其他可能的 function 都是如此。 所需要的是將Signals限制為始終是鍵/功能對的集合。 因此,使此示例工作的必要更改如下:

// add restriction to Signals
class SignalBus<Signals extends Record<string, (...args: any[]) => ()>> {
                               // remove (...args: any[]) => any &
  on<Key extends string & keyof Signals, Spec extends Signals[Key]>(
    signal: Key,
    handler: (
      signal: Signal<Parameters<Spec>, ReturnType<Spec>>,
      ...args: Parameters<Spec>
    ) => Promise<ReturnType<Spec>>,
  ): SignalConnection<Parameters<Spec>, ReturnType<Spec>> {
    // ...
    console.log("on", signal, handler);
    const connection = (handler as unknown) as SignalConnection<Parameters<Spec>, ReturnType<Spec>>;
    connection.signal = signal;
    return connection;
  }
                                         // remove (...args: any) => any &
  async emit<Key extends string & keyof Signals, Spec extends Signals[Key]>(
    signal: Key,
    ...args: Parameters<Spec>
  ): Promise<Signal<Parameters<Spec>, ReturnType<Spec>>> {
    // ...
    console.log("emit", signal, args);
    return new Signal<Parameters<Spec>, ReturnType<Spec>>(signal, args);
  }
}

操場

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM