下面是一个非常基本的设置,其中我有一个处理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
类上调用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游乐场中,如果将鼠标悬停在callback
中每次出现的handler1
上,则会看到不同的类型。
如果您执行相同的操作并将鼠标悬停在callback
中对handler2
的两个调用上,则它们的类型与在参数中明确键入的类型相同。
我的问题是
-在handler1
中,当将鼠标悬停在对callback
的调用上时,为什么TS没有显示相同的类型?特别是在handler1
中,callback
是用类型User
的参数或类型CallbackError
的参数来调用的,所以为什么callback
不是这样的推断为CallbackError | User
?
答案 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。为Handler
。 Handler
是一个函数,其第一个参数是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() + "!!");
}
personTaker1
和personTaker2
都被注释为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
的变量,反之亦然。
好的,希望能有所帮助;祝你好运!