我想扩展 Express Session 类型以允许在会话存储中使用我的自定义数据。我有一个对象 req.session.user
,它是我的类 User
的一个实例:
export class User {
public login: string;
public hashedPassword: string;
constructor(login?: string, password?: string) {
this.login = login || "" ;
this.hashedPassword = password ? UserHelper.hashPassword(password) : "";
}
}
所以我创建了我的 own.d.ts
文件以将定义与现有的快速会话类型合并:
import { User } from "./models/user";
declare module Express {
export interface Session {
user: User;
}
}
但它根本不起作用 - VS Code 和 tsc 看不到它。所以我用简单的类型创建了测试定义:
declare module Express {
export interface Session {
test: string;
}
}
并且测试字段工作正常,因此导入导致问题。
我还尝试添加 /// <reference path='models/user.ts'/>
而不是 import 但 tsc 没有看到 User 类 - 我如何在 *d.ts 文件中使用我自己的类?
编辑:我将 tsc 设置为在编译时生成定义文件,现在我有了 user.d.ts:
export declare class User {
login: string;
hashedPassword: string;
constructor();
constructor(login: string, password: string);
}
以及用于扩展 Express Session 的自己的打字文件:
import { User } from "./models/user";
declare module Express {
export interface Session {
user: User;
uuid: string;
}
}
但是当 import 声明在上面时仍然不起作用。有任何想法吗?
经过两年的 TypeScript 开发,我终于设法解决了这个问题。
基本上,TypeScript 有两种模块类型声明:“本地”(普通模块)和环境(全局)。第二种允许编写与现有模块声明合并的全局模块声明。这些文件有什么区别?
d.ts
文件仅在没有任何导入时才被视为环境模块声明。如果您提供导入行,它现在被视为普通模块文件,而不是全局文件,因此扩充模块定义不起作用。
这就是为什么我们在这里讨论的所有解决方案都不起作用的原因。但幸运的是,从 TS 2.9 开始,我们能够使用 import()
语法将类型导入全局模块声明:
declare namespace Express {
interface Request {
user: import("./user").User;
}
}
所以 import("./user").User;
行发挥了作用,现在一切正常 :)
感谢the answer from Michał Lytek。这是我在项目中使用的另一种方法。
我们可以多次导入 User
并重用它,而无需到处写 import("./user").User
,甚至可以实现它或重新导出它。
declare namespace Express {
type User = import('./user').User;
export interface Request {
user: User;
target: User;
friend: User;
}
export class SuperUser implements User {
superPower: string;
}
export { User as ExpressUser }
}
玩得开心 :)
Statements are not allowed in ambient contexts. ts(1036)
为了完整起见:
如果您有环境模块声明(即,没有任何顶级导入/导出),则它可以全局使用,无需在任何地方显式导入,但如果您有模块声明,则需要在使用者文件中导入它。
如果您想在环境模块声明中导入现有类型(从其他文件导出),则不能使用顶级导入来执行此操作(因为它不会保留环境声明)。
因此,如果您这样做:(https://stackoverflow.com/a/39132319/2054671)
// index.d.ts
import { User } from "./models/user";
declare module 'express' {
interface Session {
user: User;
uuid: string;
}
}
这将使用这个新界面扩充现有的“express”模块。 https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
但是要使用它,您必须在您的使用者文件中导入它,默认情况下它不会像环境声明一样全局可用,因为它不再是环境声明
因此,要导入从另一个文件导出的现有类型,您必须在声明块中导入它(谈到这个例子,在其他没有声明模块的例子中,您可以在其他地方导入内联)
为此,您不能使用像这样的常规导入 declare module B { import A from '../A' const a: A;因为在当前的实现中,这个导入模块的解析规则是混乱的,因此 ts 不允许这样做。这是环境模块声明中的错误导入或导出声明无法通过相对模块名称引用模块的原因。 (我无法找到相关 github 问题的链接,如果有人找到,请编辑此答案并提及。https://github.com/microsoft/TypeScript/issues/1720)请注意,您仍然可以这样做像这样:声明模块B {从'react'导入React;常量一个:A; } 因为这是绝对路径导入,而不是相对路径导入。
所以在环境模块中正确执行此操作的唯一方法是使用动态导入语法(https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#import-types)声明namespace Express { interface Request { user: import("./user").User;如接受的答案中所述(https://stackoverflow.com/a/51114250/2054671)
您还可以使用以下方式进行全局扩充: import express = require('express');从“../models/user”导入{用户};声明全局 { namespace Express { interface Session { user: User; uuid:字符串;但请记住,全局扩充只能在模块而不是环境声明中实现,因此只有在将其导入使用者文件时才有效,如@masa 的回答中所述(https://stackoverflow.com/a/55721549 /2054671)
以上所有要点对于导入从其他地方导出的模块在您的环境模块中都是有效的,但是在另一个环境模块中导入环境模块呢? (如果您想在自己的环境模块声明中使用现有环境声明并确保这些环境类型在环境模块的使用者中也可见,这将很有帮助)
您可以使用 ///
其他相关答案的链接:
在 TypeScript 中使用导入类型的环境声明
更新
从 typescript 2.9 开始,您似乎能够将类型导入全局模块。有关更多信息,请参阅接受的答案。
原始答案
我认为您面临的问题更多是关于增加模块声明然后是类类型。
导出很好,如果您尝试编译它,您会注意到:
// app.ts
import { User } from '../models/user'
let theUser = new User('theLogin', 'thePassword')
您似乎正在尝试增加 Express
的模块声明,而且您真的很接近。这应该可以解决问题:
// index.d.ts
import { User } from "./models/user";
declare module 'express' {
interface Session {
user: User;
uuid: string;
}
}
但是,这段代码的正确性当然取决于 express 声明文件的原始实现。
Import declarations in a namespace cannot reference a module.
。如果我复制粘贴您的代码,我得到:Import or export declaration in an ambient module declaration cannot reference module through relative module name.
。如果我尝试使用非相对路径,我找不到我的文件,所以我将声明文件夹移动到 node_modules 广告添加路径 "declarations/models/user"
但整个 d.ts 仍然无法正常工作 - 看不到自己的 express 扩展智能感知或 tsc 中的会话。
declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
是否不能仅遵循 express-session
的逻辑:
own.d.ts
:
import express = require('express');
import { User } from "../models/user";
declare global {
namespace Express {
interface Session {
user: User;
uuid: string;
}
}
}
在主 index.ts
中:
import express from 'express';
import session from 'express-session';
import own from './types/own';
const app = express();
app.get('/', (req, res) => {
let username = req!.session!.user.login;
});
至少这似乎编译没有任何问题。有关完整代码,请参阅 https://github.com/masa67/so39040108
tsc
不会编译它们。它们应该在编译中,但不在输出中
import type { Requestt } from 'express'
的用途吗?
请看这里:
https://stackoverflow.com/a/43688680/5412249
可以在模块中声明类型(即在使用导入/导出的文件中)并将这些类型扩充(合并)到全局命名空间中。
关键是将类型定义放在一个
declare global { ... }
以下是赛普拉斯用户熟悉的示例:
// begin file: custom_command_login.ts
import { foo } from './utils';
Cypress.Commands.add('logIn', () => {
// ...
});
// add custom command to Cypress namespace
// so that intellisense will correctly show the new command
// cy.logIn
declare global {
namespace Cypress {
interface Chainable {
logIn();
}
}
}
// end file: custom_command_login.ts