在定义文件(* d.ts)中导入类


104

我想扩展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'/>导入,但是tsc没有看到User类-如何在* d.ts文件中使用我自己的类?

编辑: 我将tsc设置为在编译时生成定义文件,现在我有了user.d.ts:

export declare class User {
    login: string;
    hashedPassword: string;
    constructor();
    constructor(login: string, password: string);
}

以及用于扩展Express Sesion的自定义文件:

import { User } from "./models/user";
declare module Express {
    export interface Session {
        user: User;
        uuid: string;
    }
}

但是当import语句放在最上面时仍然无法正常工作。有任何想法吗?

Answers:


293

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

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

d.ts仅当文件没有任何导入时,它们才被视为环境模块声明。如果提供了导入行,则现在将其视为普通模块文件,而不是全局文件,因此无法扩展模块定义。

因此,这就是我们在此讨论的所有解决方案都不起作用的原因。但幸运的是,自TS 2.9起,我们可以使用以下import()语法将类型导入全局模块声明中:

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

因此,这条线import("./user").User;起到了神奇作用,现在一切正常:)


4
这是正确的方法,至少对于打字稿的最新版本
Jefferson Tavares,

1
当声明扩展诸如Nodeprocess对象的全局模块的接口时,此方法是理想的解决方案。
特芬·埃利斯

2
谢谢,这是通过Express Middleware扩展解决我的问题的唯一明确答案!
胜介

2
谢谢@MichałLytek我想知道这种方法有任何官方文档参考吗?
Gena


5

更新

从打字稿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;
  }
}

但是,此代码的正确性当然取决于快速声明文件的原始实现。


如果我在内部移动import语句,则会出现错误: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仍然无法正常工作-在intelisense中看不到自己的快速会话扩展或tsc。
米哈尔Lytek

很抱歉,我不熟悉这些错误。也许您的设置有些不同?这会为您编译吗?gist.github.com/pellejacobs/498c997ebb8679ea90826177cf8a9bad
佩莱·雅各布斯

这样,它可以工作,但在实际应用中仍然不起作用。我有与会话对象明确请求对象,并有其他类型的声明-在命名空间中快速不模块“表达”:github.com/DefinitelyTyped/DefinitelyTyped/blob/master/...
米哈尔Lytek

5
这对我也不起作用。将导入语句添加到tsd.d.ts文件后,整个文件将停止工作。(对于该文件中定义的内容,我在应用程序的其余部分中都会出错。)
Vern Jensen

5
我有同样的问题。如果您在.d.ts中的已声明模块中使用导入,则此方法将起作用: declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
zunder 2015年

4

感谢MichałLytek的回答。这是我在项目中使用的另一种方法。

我们可以多次导入User重用它,而无需import("./user").User在任何地方写,甚至可以扩展重新导出它。

declare namespace Express {
  import("./user");  // Don't delete this line.
  import { User } from "./user";

  export interface Request {
    user: User;
    target: User;
    friend: User;
  }

  export class SuperUser extends User {
    superPower: string;
  }

  export { User as ExpressUser }
}

玩得开心 :)


0

不可能仅遵循以下逻辑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


1
您不得导入声明文件,因为tsc不会编译它们。它们应在编译中,而不是在输出中
Balint Csak
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.