简单的部分
我需要寻找每个对应中间件的DefinitelyTyped包吗?
是的,您应该为所有安装的内容安装类型定义,但不需要进行任何搜索。当您从npm安装库时,例如
npm install express-ntlm
你可以尝试安装相应的类型信息来解决它:
npm install @types/express-ntlm
如果包存在于DefinitelyTyped上,那就是它了。如果没有(因为它自己提供类型或者因为没有人为它编写类型),npm会给你返回404,这时你可以继续进行。
如果是这样的话,
Request
类型会自动附加这些属性吗?
是的,这是想要实现的效果。如果一个中间件应该增强
Request
对象,但类型定义并没有这样做,那么它们是错误的。如果这是一个流行的库,那么它们不会长时间保持错误状态。有人很可能会提交一个修复它们的DefinitelyTyped的PR。
更难的部分
为了回答你其余的问题,让它们牢记在心,你需要基本了解“声明合并”和模块与脚本之间的区别。
声明合并
在TypeScript中,一些具有相同名称的声明允许合并。特别是,接口可以与接口合并,命名空间可以与命名空间合并。这意味着你可以将它们拆分成多个单独的位置。
interface Cat {
meow(): Sound;
}
interface Cat {
name: string;
}
namespace Express {
interface Request {}
}
namespace Express {
interface Response {}
}
function doSomethingWithCat(cat: Cat) {
cat.name;
cat.meow();
}
let req: Express.Request;
let res: Express.Response;
多个
Cat
的声明将被合并在一起,您可以像使用一个统一的接口一样使用它。对于
Express
也是如此。这甚至可以跨文件工作,并且它也适用于嵌套在接口内的内容:
namespace Express {
interface Request {}
}
namespace Express {
interface Request {
myCustomFunction(): void;
}
}
模块与脚本
如果一个文件包含 import
或 export
,那么它就是一个模块。否则,TypeScript 将其视为脚本。模块具有自己的作用域,这意味着一个模块中的顶层声明无法在另一个模块中访问,除非它们被 export
(这也是整个点)。脚本是全局的,因此一个脚本中的顶层声明对其他脚本可见。
这里需要注意的是,这些说明不仅适用于变量和函数,还适用于类型和接口,并且它们也适用于你的 node_modules
中的类型声明文件(.d.ts
),而不仅仅是你自己编写的应用程序文件。
这很重要,因为它可以影响跨文件进行声明合并的方式。当我说接口可以在文件之间合并时,当其中一个或两个文件是模块时,需要进行更多的工作,因为它们默认是隔离的。让我们重新审视一下以 a.ts
和 b.ts
为例的上一个示例,但这次将 b.ts
变为一个模块:
namespace Express {
interface Request {}
}
import express from 'express';
namespace Express {
interface Request {
myCustomFunction(): void;
}
}
我们的声明合并已经停止工作了,因为我们在两个完全不同的作用域中声明了
Express
:全局作用域和
b.ts
模块作用域。我们需要一种逃离
b.ts
模块作用域的方法:
import express from 'express';
declare global {
namespace Express {
interface Request {
myCustomFunction(): void;
}
}
}
将所有内容整合起来
在这种情况下,我需要声明并使用myCustomFunction
扩展Request
吗?
是的,看起来您已经掌握了这个部分。如果您写的代码在一个带有import
或export
的文件中,那么它就无法工作,您需要将其包装在declare global
中。之所以能够实现这一点,是因为@types/express-serve-static-core
会自动包含在@types/express
中,它为您设置了Express.Request
。然后,他们扩展了基础类型,包括所有内置的express内容(例如:get
、header
、param
等),并在其余的定义中引用该类型。(我必须承认,如果没有人告诉您Express.Request
已经存在并准备好供您扩展,那么要确定这一点将非常困难,但看起来在来到这里之前您已经弄清楚了这一点。)
此外,我扩展的Request
是否会“包含”DefinitelyTyped中给出的类型?
现在您已经了解了声明合并并且已经看到您正在合并的内容,您可以看到答案是技术上不会:您正在与一个空接口进行合并,因此Express.Request
将包括您放置在其中以及其他中间件类型放置在其中的内容,但不会包括核心express内容。但这并不重要,因为路由处理程序中req
的类型扩展了Express.Request
,所以此时的答案是是的,该类型应该包含所有来自核心express类型、所有中间件类型和您自己的自定义扩展的内容:
![全局增强将属性添加到Express.Request并出现在完成中](https://istack.dev59.com/cWonj.webp)
在使用时,我该如何引用此接口?是 Express.Request
吗?还是只有 Request
?
正如我们所看到的,Express.Request
仅包含增强部分而不是核心的 express 内容。完整的 Request
类型则从 express
包中导出,因此您可以像这样引用它:
import express from 'express';
import * as express from 'express';
import express = require('express');
function doSomethingWithRequest(req: express.Request) { ... }
或者
import { Request } from 'express';
但通常最好的方式是根本不进行明确的参考:
import express from 'express';
const app = express();
app.get('/', req => {
req.myCustomFunc();
});
(令人困惑的是,全局的“Request”类型与express无关。)
如果我将其标记为“Request”,TypeScript如何知道要使用“我的”Request而不是Express的DefinitelyTyped库导出的Request?
因为您已经了解了声明合并,所以这是一个无意义的问题:您的声明与DefinitelyTyped包中的声明合并,创建了一个Request。(导出的Request是一个单独的类型,扩展了全局的Express.Request,这是一个不幸的分心,从这个简单的事实中分散了注意力。)由于它们已经合并,如果您想要引用它们,就不能分别引用它们。
@types/express/index.d.ts
中的src
之外,并添加了 tsconfig 中的typeRoots: ["@types", "./node_modules/@types"]
。即使遵循上述所有步骤,我仍然会得到一个生气的红线,直到我在 tsconfig 的types
中加入了express
。 - Phil D.