打字稿混淆:如何使用自定义和绝对类型库扩展 Express `Request`?
Posted
技术标签:
【中文标题】打字稿混淆:如何使用自定义和绝对类型库扩展 Express `Request`?【英文标题】:Typescript confusion: How to extend Express `Request` with custom and DefinitelyTyped lib? 【发布时间】:2021-01-04 18:15:07 【问题描述】:如何在 Typescript/Express 中扩展和使用扩展的Request
类型?
我正在添加一组 extend
Request
对象的中间件库。
IE。一位在请求中添加了user
。另一个添加cookie()
到req。另一个添加csrfToken()
到req。等等。
当我添加一个请求处理函数时,如何告诉该函数使用 req
以及中间件添加的所有花里胡哨?
我是否会搜索与中间件对应的每个 DefinitelyTyped
包?如果是这样,那么Request
类型会被这些属性神奇地“装饰”吗?
为了更难,我编写了自己的中间件,为Request
添加属性
req.myCustomFunction()
在这种情况下,我是否需要使用 myCustomFunction 自己声明和扩展 Request
?
此外,我扩展的Request
是否会“包含”DefinitelyTyped
给出的类型?
declare namespace Express
export interface Request
myCustomFunction: () => void
现在这会包括通过DefinitelyTyped
和我的myCustomFunction
包含的所有属性吗?
使用时如何引用这个接口?
会是Express.Request
吗?还是只是Request
?
如果我将其引用为 Request
,Typescript 怎么知道使用“我的”请求而不是 Express 的 DefinitelyTyped
库导出的请求?
【问题讨论】:
【参考方案1】:简单的部分
我是否会寻找与中间件对应的每个DefinitelyTyped 包?
是的,你应该为你安装的所有东西安装类型定义,但是不,不应该做任何狩猎。当你从 npm 安装一个库时,说
npm install express-ntlm
您可以尝试为其安装类型来跟进:
npm install @types/express-ntlm
如果该包存在于 distinctlyTyped 上,那就是它。如果没有(因为它提供了自己的类型或者因为没有人为它编写类型),npm 会给你一个 404,你可以继续。
如果是这样,那么
Request
类型会被这些属性神奇地“装饰”吗?
是的,就是这样。如果一个中间件应该增加 Request
对象,但类型不这样做,那么它们是错误的。如果它是一个受欢迎的图书馆,他们不会长期出错。有人可能会提交 PR 给 DefinitiveTyped 修复它。
较难的部分
要以一种能够坚持的方式回答您的其余问题,您需要对声明合并有基本的了解。它还有助于理解 modules 和 scripts 之间的区别。
声明合并
在 TypeScript 中,允许合并某些具有相同名称的声明。特别是,允许接口与接口合并,并且允许命名空间与命名空间合并。这意味着您可以将它们分成多个单独的位置:
interface Cat
meow(): Sound;
interface Cat
name: string;
namespace Express
interface Request
namespace Express
interface Response
function doSomethingWithCat(cat: Cat)
cat.name; // string
cat.meow(); // Sound
let req: Express.Request;
let res: Express.Response;
Cat
的多个声明合并在一起,您可以像使用一个统一的接口一样使用它。 Express
也是如此。这甚至适用于跨文件,它也适用于嵌套在接口中的东西:
// File: a.ts
namespace Express
interface Request
// File: b.ts
// If I want to add a property to `Express.Request` in a.ts, I have to merge
// both the namespace and the interface:
namespace Express
interface Request
myCustomFunction(): void;
模块与脚本
如果文件包含import
或export
,则它是一个模块。如果不是,TypeScript 认为它是一个脚本。模块有自己的作用域,这意味着一个模块中的***声明不能在另一个模块中访问,除非它们是export
ed(这是重点)。脚本是全局的,因此一个脚本中的任何***声明都可以被其他脚本访问。
这里的棘手之处在于,这些注释不仅适用于变量和函数,还适用于类型和接口,它们也适用于 node_modules
内的类型声明文件 (.d.ts
),而不仅仅是应用程序文件你自己写。
这很重要,因为它会影响跨文件合并声明的工作方式。当我说接口可以跨文件合并时,当一个或两个文件都是模块时,这样做需要做更多的工作,因为默认情况下它们是隔离的。让我们用a.ts
和b.ts
重温前面的例子,但这一次,我们将b.ts
做成一个模块:
// File: a.ts
namespace Express
interface Request
// File: b.ts
import express from 'express';
// Oops, this only creates a *local* declaration
// called Express. It doesn’t actually merge with a.ts,
// because I’m in a module scope here.
namespace Express
interface Request
myCustomFunction(): void;
我们的声明合并已停止工作,因为我们在两个完全不同的范围内声明 Express
:全局范围和 b.ts 的模块范围。我们需要一种从 b.ts 中“逃离”模块范围的方法:
// File: b.ts
import express from 'express';
// Now it merges with Express.Request in a.ts!
declare global
namespace Express
interface Request
myCustomFunction(): void;
把它们放在一起
在这种情况下,我需要用
myCustomFunction
自己声明和扩展Request
吗?
是的,看起来您已经完成了这部分。您编写的 sn-p 看起来是正确的如果它出现在脚本中。如果您编写它的文件有import
或export
,它将不再工作,您需要将其包装在declare global
中。这个工作的原因是@types/express-serve-static-core
,它被@types/express
自动包含,sets Express.Request
up for you合并。然后,他们 extend that base type 使用所有内置的 express 内容(get
、header
、param
等)并在其余定义中引用该类型。 (我承认,如果没有人告诉你它在那里,很难确定 Express.Request
在那里并准备好让你扩展它,但看起来你在来这里之前就想好了。)
此外,我正在扩展的
Request
是否会“包含”DefinitelyTyped 给出的类型?
既然您了解了声明合并并且已经看到 what you’re merging with,您可以看到答案是技术上否:您正在与一个空接口合并,所以 Express.Request
将包含什么你把它和其他中间件类型放在它上面,但不是核心表达的东西。但这没关系,因为路由处理程序中req
的类型扩展 Express.Request
,所以在这一点上,答案是是,该类型应该包含从核心快速类型、所有中间件类型和您自己的自定义增强中的所有内容:
使用时如何引用这个接口?会是
Express.Request
吗?还是只是Request
?
正如我们所见,Express.Request
可作为全局变量使用,它将仅包含增强功能,而不包含核心快递内容。 complete Request
类型是从 express
包中导出的,因此您可以像这样引用它:
import express from 'express';
// Or, depending on your compiler settings:
import * as express from 'express';
// Or yet again:
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(); // 'req' is contextually typed by `app.get`, and has what you want
);
(令人困惑的是,global Request
类型与 express 完全无关。)
如果我将其引用为
Request
,Typescript 怎么知道使用“我的”Request
而不是 Express 的绝对类型库导出的那个?
因为您已经了解了声明合并,所以您现在知道这是一个空问题:您的声明 merged 与DefinitelyTyped 包中的声明合并以创建one @ 987654372@。 (导出的 Request
是一个扩展全局 Express.Request
的单独类型这一事实令人遗憾地分散了对这个简单事实的注意力。)因为它们已合并,如果您愿意,您不能单独引用它们。
【讨论】:
我在@types/express/index.d.ts
中有src
之外的类型扩展,并添加了tsconfig typeRoots: ["@types", "./node_modules/@types"]
。即使遵循上述所有内容,我也会得到一条愤怒的红线,直到我将 express
添加到 tsconfig types
。
这是一个很好的答案。【参考方案2】:
我在我的项目中创建了一个@types
文件夹并添加了以下代码:
@types/express/index.d.ts
import Express from "express-serve-static-core";
declare module "express-serve-static-core"
interface Request
... custom properties and methods here ...
这让我可以使用我想要的任何属性和方法来扩展 Request
类型
【讨论】:
什么是express-serve-static-core
?
@types/express-serve-static-core
是一个包含type definitions 用于快递的包。以上是关于打字稿混淆:如何使用自定义和绝对类型库扩展 Express `Request`?的主要内容,如果未能解决你的问题,请参考以下文章