Typescript 混淆:如何使用自定义和 DefinelyTyped 库扩展 Express `Request`?

GN.*_*GN. 5 express typescript

如何Request在 Typescript / Express 中扩展和使用扩展类型?

我添加了一堆中间件库,其中extendObject Request. IE One 添加user到要求中。另一个添加cookie()到需求。另一个添加csrfToken()到需求。ETC..

当我添加请求处理函数时,如何告诉该函数使用req中间件添加的所有功能?

我是否要查找DefinitelyTyped与中间件对应的每个包?如果是这样,那么该Request类型是否会被这些属性神奇地“装饰”?

为了让它变得更加困难,我编写了自己的中间件,将属性添加到Request

req.myCustomFunction()

在这种情况下,我需要声明并Request使用 myCustomFunction 扩展 self 吗?

此外,Request我扩展的内容是否会“包括”给出的类型DefinitelyTyped?

declare namespace Express {
  export interface Request {
    myCustomFunction: () => void
  }
}
Run Code Online (Sandbox Code Playgroud)

DefinitelyTyped现在这将包括通过AND my包含的所有属性吗myCustomFunction?

使用时如何引用这个接口?

那将会Express.Request?要不就Request?

如果我将其引用为Request,Typescript 如何知道使用“我的”请求而不是 ExpressDefinitelyTyped库导出的请求?

And*_*rew 10

简单的部分

\n
\n

我是否要查找与中间件对应的每个DefinitelyTyped 包?

\n
\n

是的,您应该为您安装的所有内容安装类型定义,但是不,\xe2\x80\x99 不应该有任何搜索要做。当你从 npm 安装库时,比如说

\n
npm install express-ntlm\n
Run Code Online (Sandbox Code Playgroud)\n

您可以尝试为其安装类型来跟进:

\n
npm install @types/express-ntlm\n
Run Code Online (Sandbox Code Playgroud)\n

如果该包存在于 DefinelyTyped 上,那就是它。如果它没有\xe2\x80\x99t(因为它有自己的类型或者因为没有人为它编写类型),npm 会给你一个 404,你可以继续。

\n
\n

如果是这样,那么该Request类型是否会被这些属性神奇地“装饰”?

\n
\n

是的,这\xe2\x80\x99就是这个想法。如果中间件应该增强Request对象,但类型不\xe2\x80\x99t 这样做,那么它们是错误的。如果它是一个流行的库,那么它们就不会长期出错。有人可能会向DefinitelyTyped 提交修复该问题的 PR。

\n

较难的部分

\n

为了以持久的方式回答其余问题,您需要对声明合并有基本的了解。它还有助于理解模块和脚本之间的区别。

\n

声明合并

\n

在 TypeScript 中,允许合并某些类型的同名声明。特别是,允许​​接口与接口合并,并且允许命名空间与命名空间合并。这意味着您可以将它们分成多个单独的位置:

\n
interface Cat {\n  meow(): Sound;\n}\n\ninterface Cat {\n  name: string;\n}\n\nnamespace Express {\n  interface Request {}\n}\n\nnamespace Express {\n  interface Response {}\n}\n\nfunction doSomethingWithCat(cat: Cat) {\n  cat.name; // string\n  cat.meow(); // Sound\n}\n\nlet req: Express.Request;\nlet res: Express.Response;\n
Run Code Online (Sandbox Code Playgroud)\n

的多个声明Cat合并在一起,您可以像使用一个统一的接口一样使用它。也是如此Express。这甚至可以跨文件工作,并且也可以处理嵌套在接口中的内容:

\n
// File: a.ts\nnamespace Express {\n  interface Request {}\n}\n\n// File: b.ts\n// If I want to add a property to `Express.Request` in a.ts, I have to merge\n// both the namespace and the interface:\nnamespace Express {\n  interface Request {\n    myCustomFunction(): void;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

模块与脚本

\n

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

\n

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

\n

这很重要,因为它会影响跨文件声明合并的工作方式。当我说接口可以跨文件合并时,当一个或两个文件都是模块时,需要做更多的工作才能做到这一点,因为它们默认是隔离的。a.ts让\xe2\x80\x99s 使用和重新审视前面的示例b.ts,但这一次,我们将创建b.ts一个模块:

\n
// File: a.ts\nnamespace Express {\n  interface Request {}\n}\n\n// File: b.ts\nimport express from \'express\';\n\n// Oops, this only creates a *local* declaration\n// called Express. It doesn\xe2\x80\x99t actually merge with a.ts,\n// because I\xe2\x80\x99m in a module scope here.\nnamespace Express {\n  interface Request {\n    myCustomFunction(): void;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我们的声明合并已停止工作,因为我们\xe2\x80\x99Express在两个完全不同的作用域中声明:全局作用域和b.ts\xe2\x80\x99s模块作用域。我们需要一种从 b.ts 中 \xe2\x80\x9cescape\xe2\x80\x9d 模块范围的方法:

\n
// File: b.ts\nimport express from \'express\';\n\n// Now it merges with Express.Request in a.ts!\ndeclare global {\n  namespace Express {\n    interface Request {\n      myCustomFunction(): void;\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

把它们放在一起

\n
\n

在这种情况下,我需要声明并扩展Requestself 吗myCustomFunction?

\n
\n

是的,看起来你\xe2\x80\x99已经把这部分写下来了。如果您编写的代码片段出现在脚本中,则它看起来是正确的。如果您编写的文件有import或export,则\xe2\x80\x99 将不再工作,并且您\xe2\x80\x99 将需要将其包装在declare global. 这样做的原因是@types/express-serve-static-core,它自动包含在 中@types/express,为您设置合并Express.Request。然后,他们使用所有内置的表达内容(、、等)扩展该基本类型,并在其定义的其余部分中引用该类型。(我承认,如果没有人告诉你它在那里,那么很难确定它在那里并准备好延长,但看起来你在来这里之前就已经弄清楚了。)getheaderparamExpress.Request

\n
\n

另外,Request我扩展的内容是否会包含DefinitelyTyped 给出的类型?

\n
\n

现在您了解了声明合并并了解了您\xe2\x80\x99re 与什么合并,您可以看到答案在技术上是否定的:您\xe2\x80\x99re 与一个空接口合并,因此Express.Request将包括您放置的内容它以及其他中间件类型放在它上面,但不是核心的 Express 东西。但这并不重要,因为req路由处理程序中的类型扩展了 Express.Request,所以在这一点上,答案是肯定的,该类型应该包含核心表达类型、所有中间件类型和你自己的所有内容自定义增强:

\n

全局增强将属性添加到 Express.Request 并出现在补全中

\n
\n

使用时如何引用这个接口?那将会Express.Request?要不就Request?

\n
\n

正如我们所看到的,Express.Request作为全局可用的 ,将仅包含增强内容,而不包含核心表达内容。完整的 类型Request是从express包中导出的,因此您可以像这样引用它:

\n
import express from \'express\';\n// Or, depending on your compiler settings:\nimport * as express from \'express\';\n// Or yet again:\nimport express = require(\'express\');\n\nfunction doSomethingWithRequest(req: express.Request) { ... }\n
Run Code Online (Sandbox Code Playgroud)\n

或者

\n
import { Request } from \'express\';\n
Run Code Online (Sandbox Code Playgroud)\n

但最好的方法通常是根本不明确引用:

\n
import express from \'express\';\nconst app = express();\napp.get(\'/\', req => {\n  req.myCustomFunc(); // \'req\' is contextually typed by `app.get`, and has what you want\n});\n
Run Code Online (Sandbox Code Playgroud)\n

(令人困惑的是,全局 Request类型与express完全无关。)

\n
\n

如果我将其引用为Request,Typescript 如何知道使用“我的”Request而不是 Express\ 的 DefinelyTyped 库导出的那个?

\n
\n

因为您\xe2\x80\x99已经了解了声明合并,所以您现在知道这是一个空问题:您的声明与DefiniteTyped包中的声明合并以创建一个 Request。(导出的类型Request是扩展全局的单独类型,Express.Request这一事实不幸地分散了人们对这个简单事实的注意力。)因为它们已经合并,所以如果您愿意,您无法单独引用它们。

\n


ran*_*tao 3

我@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 ...
    }
}
Run Code Online (Sandbox Code Playgroud)

Request这使我能够使用我想要的任何属性和方法来扩展类型