在TypeScript中显式键入泛型函数参数和隐式键入之间有什么区别?

时间:2019-12-29 05:56:46

标签: typescript

下面是一个非常基本的设置,其中我有一个处理HTTP请求的类App(可以想象,下面没有显示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类上调用RouterRouter中的路由只是被调用的回调,仅此而已,该回调的类型为HandlerHandler将另一个回调作为参数,类型为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游乐场中,如果将鼠标悬停在callback中每次出现的handler1上,则会看到不同的类型。

如果您执行相同的操作并将鼠标悬停在callback中对handler2的两个调用上,则它们的类型与在参数中明确键入的类型相同。

我的问题是 -在handler1中,当将鼠标悬停在对callback的调用上时,为什么TS没有显示相同的类型?特别是在handler1中,callback是用类型User的参数或类型CallbackError的参数来调用的,所以为什么callback不是这样的推断为CallbackError | User

2 个答案:

答案 0 :(得分:0)

  

在处理程序1中,使用用户类型的参数或类型CallbackError的参数调用回调

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

const foo = { id: 1 };
type Foo = typeof foo; // { id: number } not User

答案 1 :(得分:0)

以面值回答您的问题:在handler1中,callback参数没有给出明确的类型注释,因此可以从handler1的注释类型中推断出contextually。为HandlerHandler是一个函数,其第一个参数是HandlerCallback,因此callback被推断为HandlerCallback

类型HandlerCallback是通用函数,因此在handler1内部,callback参数将被视为此类通用函数。每次调用它时,都会指定其类型参数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 } }的全部主体,但不是被推断为较窄的类型personTaker1,并且是{{ 1}}。这就是在TypeScript中函数参数的上下文类型推断的工作方式。


这是否更清楚?如果您了解为什么将person推断为personTaker2而不是person中的Person,但是仍然有问题,将{ name: { toUpperCase(): string } }推断为{{1} },而不是personTaker1中的callback,那么您可能需要澄清一下您的问题。特别是,请确保您了解泛型 type 和非泛型类型之间的区别,泛型 type 指的是非泛型函数,例如HandlerCallback,而泛型的类型是指泛型函数,例如{ {1}}。这些是不同的类型。例如,类型(payload: CallbackError | User) => void的值可分配给类型handler1的变量,反之亦然。


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

Link to code

相关问题