1

下面是一个非常基本的设置,其中我有一个App处理 HTTP 请求的类(想象一下,HTTP 服务器的代码未在下面显示)。

interface CallbackError {
  error: string;
}

type HandlerCallback = <T>(payload?: CallbackError | T) => void;

type Handler = (callback: HandlerCallback) => void;


// omitted code here is a server that calls handleRequest on a request
class App {
  public constructor(private router: Router) {
    this.router = router;
  }

  private handleRequest = () => {
    this.router.handleRequest(this.sendResponse);
  };

  private sendResponse: HandlerCallback = (payload?: any) => {
    // do something
  };
}

当一个请求进来时,在类handleRequest上被调用。Router中的路由Router只是一个被调用的回调,仅此而已,而且这个回调的类型是Handler. Handler将另一个回调作为参数,类型为HandlerCallback.

class Router {
  private routes: Handler[] = [];

  public addRoute = (callback: Handler) => {
    this.routes.push(callback)
  };

  // the request is handled by simply calling the callbacks added
  public handleRequest = (callback: HandlerCallback) => {
      this.routes.forEach(cb => cb(callback))
  };
}

interface User { id: number }

const handler1: Handler = (callback) => {
  if (Math.random() > 0.5) {
    callback({ id: 1 })
  } else {
    callback({ error: "error" })
  }
}

const handler2: Handler = (callback: (payload: CallbackError | User) => void) => {
  if (Math.random() > 0.5) {
    callback({ id: 1 })
  } else {
    callback({ error: "error" })
  }
}

const router = new Router();

router.addRoute(handler1)
router.addRoute(handler2)

正如您在上面看到的,我定义了两个“路由”,即Handler路由器要调用的两个回调,它们每个都带有一个回调参数类型HandlerCallback我的问题与如何键入后者的回调有关。handler1中,我将参数隐式类型化,因此HandlerCallback使用泛型类型的定义对其进行类型化。在 TS Playground 中,如果您将鼠标悬停在每次出现的callbackin 上handler1,您会看到不同的类型。

如果您执行相同操作并将鼠标悬停在对 in 的两个调用上callbackhandler2它们的类型与在参数中显式键入的类型相同。

所以我的问题是 -在handler1,为什么当您将鼠标悬停在调用上时 TS 不显示相同的类型callback?特别是, inhandler1callback使用 type 的User参数或 type 的参数调用的,那么为什么推断CallbackError的类型不是 is ?callbackCallbackError | User

4

2 回答 2

1

从表面上看你的问题:在 中handler1callback参数没有明确的类型注释,因此是从as的注释类型中推断出来。A是一个函数,其第一个参数是 a ,因此推断为。handler1HandlerHandlerHandlerCallbackcallbackHandlerCallback

类型HandlerCallback是泛型函数,所以在里面handler1callback参数会被当成这样的泛型函数。每次调用它时,T都会指定它的类型参数……如果你不手动执行,它会从函数的参数中推断出来。因此,您会得到以下行为:

callback({ id: 1 }) // hover
/* callback: <{ id: number; }>(
    payload?: CallbackError | { id: number;} | undefined
   ) => void */

callback({ error: "error" }) // hover
/* callback: <{ error: string; }>(
     payload?: CallbackError | { error: string;} | undefined
   ) => void */

乃至

callback("hello") // hover
/* callback: <string>(payload?: string | CallbackError | undefined) => void */

这只是正常的通用函数行为。


如果您希望callback根据您实际调用它的内容在函数内部进行推断,那么上下文类型不会以这种方式工作。让我们看一个具有相同行为但没有泛型的不同示例:

interface Person {
    name: string,
    age: number
}
type PersonTaker = (person: Person) => void;

const personTaker1: PersonTaker = person => {
    // person inferred as Person, not {name: {toUpperCase(): string}}
    console.log("HELLO " + person.name.toUpperCase() + "!!");
}

const personTaker2: PersonTaker = (person: { name: { toUpperCase(): string } }) => {
    console.log("HELLO " + person.name.toUpperCase() + "!!");
}

两者personTaker1personTaker2都被注释为PersonTaker函数。但是personTaker1允许person编译器推断其参数,同时personTaker2显式注释其person参数。在personTaker1中,类型person被推断为Person,根据上下文从类型中推断出来PersonTaker。它没有被推断为较窄的类型{ name: { toUpperCase(): string } },即使这是所有使用的主体,并且是inpersonTaker1的显式注释类型。这就是 TypeScript 中函数参数的上下文类型推断的工作原理。personpersonTaker2


这是否使它更清楚?如果您理解为什么person推断为 asPerson而不是 as { name: { toUpperCase(): string } }in personTaker1,但仍然存在callback推断为 asHandlerCallback而不是 as (payload: CallbackError | User) => voidin的问题handler1,那么您可能需要稍微澄清一下您的问题。特别是,请确保您了解引用非泛型函数(如 )的泛型类型与引用泛型函数(如type Foo<T> = (x: T)=>void的非泛型类型之间的区别type Bar = <T>(x: T)=>void。这些是不同的类型。例如, type 的值Bar可以分配给 type 的变量Foo<string>,但反之则不行。


好的,希望有帮助;祝你好运!

链接到代码

于 2019-12-31T04:10:59.467 回答
0

在 handler1 中,使用 User 类型的参数或 CallbackError 类型的参数调用回调

不准确,{ id: 1 }具有与 a 相同的形状User,但代码中没有任何内容实际上使/断言它是User

const foo = { id: 1 };
type Foo = typeof foo; // { id: number } not User
于 2019-12-29T06:58:40.707 回答