我想扩展快速会话类型,以允许在会话存储中使用自定义数据。我有一个对象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语句在顶部时仍然不工作。什么好主意吗?


当前回答

请看这里:

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

其他回答

更新

从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;做的魔法,现在一切工作:)

请看这里:

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

难道就不能遵循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 }
}

玩得开心!