打字稿混淆:如何使用自定义和绝对类型库扩展 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 修复它。

较难的部分

要以一种能够坚持的方式回答您的其余问题,您需要对声明合并有基本的了解。它还有助于理解 modulesscripts 之间的区别。

声明合并

在 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;
  

模块与脚本

如果文件包含importexport,则它是一个模块。如果不是,TypeScript 认为它是一个脚本。模块有自己的作用域,这意味着一个模块中的***声明不能在另一个模块中访问,除非它们是exported(这是重点)。脚本是全局的,因此一个脚本中的任何***声明都可以被其他脚本访问。

这里的棘手之处在于,这些注释不仅适用于变量和函数,还适用于类型和接口,它们也适用于 node_modules 内的类型声明文件 (.d.ts),而不仅仅是应用程序文件你自己写。

这很重要,因为它会影响跨文件合并声明的工作方式。当我说接口可以跨文件合并时,当一个或两个文件都是模块时,这样做需要做更多的工作,因为默认情况下它们是隔离的。让我们用a.tsb.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 看起来是正确的如果它出现在脚本中。如果您编写它的文件有importexport,它将不再工作,您需要将其包装在declare global 中。这个工作的原因是@types/express-serve-static-core,它被@types/express自动包含,sets Express.Request up for you合并。然后,他们 extend that base type 使用所有内置的 express 内容(getheaderparam 等)并在其余定义中引用该类型。 (我承认,如果没有人告诉你它在那里,很难确定 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`?的主要内容,如果未能解决你的问题,请参考以下文章

打字稿数组混淆 - TS 2488 类型为“从不”

如何使用第三方库提供的类型? (打字稿,Element-UI)

在打字稿中带有参数类型的角度 ui-state

为啥打字稿抱怨对象必须是扩展类型中的对象

扩展打字稿中的快速请求类型

如何使用打字稿为反应测试库设置智能感知