我想扩展快速会话类型,以允许在会话存储中使用自定义数据。我有一个对象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) : "";
    }
}

所以我创建了自己的n.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'/>代替导入,但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 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;
  }
}

然而,这段代码的正确性当然取决于快速声明文件的原始实现。


经过两年的TypeScript开发,我终于设法解决了这个问题。

基本上,TypeScript有两种模块类型声明:“local”(普通模块)和ambient(全局模块)。第二类允许编写与现有模块声明合并的全局模块声明。这些文件之间有什么不同?

D.ts文件只有在没有任何导入的情况下才被视为环境模块声明。如果你提供了一个导入行,它现在会被当作一个普通的模块文件,而不是全局文件,所以扩充模块定义是不起作用的。

这就是为什么我们在这里讨论的所有解决方案都不管用。但幸运的是,自TS 2.9以来,我们能够使用import()语法将类型导入到全局模块声明中:

declare namespace Express {
  interface Request {
    user: import("./user").User;
  }
}

输入import("./user"). user;做的魔法,现在一切工作:)


难道就不能遵循express-session的逻辑吗:

own.d.ts:

import express = require('express');
import { User } from "../models/user";

declare global {
    namespace Express {
        interface Session {
            user: User;
            uuid: string;
        }
    }
}

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


感谢米夏沃勒泰克的回答。这是我在项目中使用的另一种方法。

我们可以导入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 }
}

玩得开心!


为了完整起见:

如果你有一个环境模块声明(即,没有任何顶级导入/导出),它是全局可用的,不需要显式地将它导入到任何地方,但如果你有一个模块声明,你将需要在消费者文件中导入它。 如果你想在你的环境模块声明中导入一个现有的类型(它是从其他文件导出的),你不能用顶级导入(因为那样它就不是环境声明了)。

如果你这样做: (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

但是要使用这个,你必须在你的消费者文件中导入这个,它不会像环境声明一样默认全局可用,因为它不再是一个环境声明

so, to import an existing type exported from another file, you have to import it inside the declare block (talking about this example, in other examples where you are not declaring a module, you can import inline at other places) to do this, you cannot use a regular import like this declare module B { import A from '../A' const a: A; } because in current implementation, the rules for resolution of this imported module are confusing, and hence ts does not allow this. This is the reason for the error Import or export declaration in an ambient module declaration cannot reference module through relative module name. (I am not able to find the link to the relevant github issue, if someone finds it, please edit this answer and mention. https://github.com/microsoft/TypeScript/issues/1720) Please note, you can still do something like this: declare module B { import React from 'react'; const a: A; } because this is an absolute path import and not a relative path import. so the only way to correctly do this in an ambient module is using the dynamic import syntax (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#import-types) declare namespace Express { interface Request { user: import("./user").User; } } as mentioned in the accepted answer (https://stackoverflow.com/a/51114250/2054671) you can also do a global augmentation with something like this: import express = require('express'); import { User } from "../models/user"; declare global { namespace Express { interface Session { user: User; uuid: string; } } } but remember global augmentation is only possible in a module not an ambient declaration, so this would work only if you import it in the consumer file, as mentioned in @masa's answer (https://stackoverflow.com/a/55721549/2054671)


以上所有要点都适用于导入从其他地方导出的模块,在您的环境模块中,但是在另一个环境模块中导入一个环境模块呢?(如果你想在你自己的环境模块声明中使用一个现有的环境声明,并确保这些环境类型在你的环境模块的消费者中也可见,这是很有帮助的)

你可以使用/// <引用类型="../.. "/a" />指令 / / ambientA.d.ts 接口A { t:字符串 } / / ambientB.d.ts /// <引用类型="../ambientA.d. "ts " / > 声明模块B { a: a; 导出{a}; }

其他相关答案的链接:

在TypeScript中使用导入的类型进行环境声明


请看这里:

https://stackoverflow.com/a/43688680/5412249

可以在模块中(即在使用导入/导出的文件中)声明类型,并将这些类型扩展(合并)到全局命名空间中。

关键是将类型定义放在

declare global { ... }

下面是Cypress用户熟悉的示例:

// 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