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

Mic*_*tek 50 typescript typescript-typings

我想扩展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) : "";
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我创建了我的own.d.ts文件以将定义与现有的快速会话类型合并:

import { User } from "./models/user";

declare module Express {
    export interface Session {
        user: User;
    }
}
Run Code Online (Sandbox Code Playgroud)

但它根本不起作用 - VS Code和tsc看不到它.所以我用简单的类型创建了测试定义:

declare module Express {
    export interface Session {
        test: string;
    }
}
Run Code Online (Sandbox Code Playgroud)

并且测试字段工作正常,因此导入导致问题.

我也尝试添加/// <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);
}
Run Code Online (Sandbox Code Playgroud)

以及用于扩展Express Sesion的自己的输入文件:

import { User } from "./models/user";
declare module Express {
    export interface Session {
        user: User;
        uuid: string;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是当import语句在顶部时仍然无法正常工作.有任何想法吗?

Mic*_*tek 113

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

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

d.ts仅当文件没有任何导入时,才将文件视为环境模块声明.如果您提供导入行,则它现在被视为普通模块文件,而不是全局模块文件,因此扩充模块定义不起作用.

这就是为什么我们在这里讨论的所有解决方案都不起作用的原因.但幸运的是,从TS 2.9开始,我们可以使用import()语法将类型导入到全局模块声明中:

declare namespace Express {
  interface Request {
    user: import("./user").User;
  }
}
Run Code Online (Sandbox Code Playgroud)

所以这条线import("./user").User;做了魔术,现在一切正常:)

  • 这是正确的方法,至少对于最新版本的打字稿而言 (3认同)
  • @Gena https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#import-types (3认同)
  • 谢谢,这是解决我的 Express Middleware 扩展问题的唯一明确答案! (2认同)
  • 谢谢@Michał Lytek 我想知道这种方法是否有任何官方文档参考? (2认同)
  • 为什么我们需要使用 import() 语法,为什么不在声明块中使用常规导入? (2认同)

h00*_*00w 18

感谢Micha 的回答?莱泰克。这是我在项目中使用的另一种方法。

我们可以多次导入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 }
}
Run Code Online (Sandbox Code Playgroud)

玩得开心 :)


gau*_*430 12

为了完整起见:

  • 如果您有环境模块声明(即,没有任何顶级导入/导出),则无需在任何地方显式导入它即可全局使用,但如果您有模块声明,则需要将其导入消费者文件中。
  • 如果要在环境模块声明中导入现有类型(从其他文件导出),则不能使用顶级导入(因为这样它就不会保持环境声明)。

所以如果你这样做:(/sf/answers/2739262361/

// index.d.ts
import { User } from "./models/user";
declare module 'express' {
  interface Session {
    user: User;
    uuid: string;
  }
}
Run Code Online (Sandbox Code Playgroud)

这将使用这个新界面扩充现有的“express”模块。https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

但是要使用它,您必须将其导入您的消费者文件中,默认情况下它不会像环境声明那样全局可用,因为它不再是环境声明

  • 因此,要导入从另一个文件导出的现有类型,您必须将其导入到declare块内(说到这个例子,在其他没有声明模块的例子中,您可以在其他地方内联导入)

  • 为此,您不能像这样使用常规导入

    declare module B {
      import A from '../A'
      const a: A;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因为在当前的实现中,这个导入模块的解析规则令人困惑,因此 ts 不允许这样做。这就是错误的原因Import or export declaration in an ambient module declaration cannot reference module through relative module name. (我找不到相关 github 问题的链接,如果有人找到了,请编辑此答案并提及。https://github.com/microsoft/TypeScript/issues/1720

    请注意,您仍然可以执行以下操作:

    declare module B {
      import React from 'react';
      const a: A;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因为这是绝对路径导入而不是相对路径导入。

  • 所以在环境模块中正确执行此操作的唯一方法是使用动态导入语法(https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#import-types

    declare namespace Express {
     interface Request {
       user: import("./user").User;
     }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    如接受的答案中所述(/sf/answers/3577997531/

  • 您还可以使用以下方法进行全局增强:

    import express = require('express');
    import { User } from "../models/user";
    
    declare global {
        namespace Express {
            interface Session {
                user: User;
                uuid: string;
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但请记住,全局增强只能在模块中而不是环境声明中进行,因此只有在您将其导入消费者文件时才有效,如@masa 的回答中所述(/sf/answers/3900508461/


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

其他相关答案的链接:

  • 我注意到您在三斜杠引用标记中使用相对路径,如果我想引用 node_modules 文件夹内的 .d.ts 文件怎么办? (3认同)

Pel*_*obs 5

更新

从 typescript 2.9 开始,您似乎可以将类型导入到全局模块中。有关更多信息,请参阅已接受的答案。

原答案

我认为您面临的问题更多是关于增加模块声明然后是类类型。

导出很好,如果您尝试编译它,您会注意到:

// app.ts  
import { User } from '../models/user'
let theUser = new User('theLogin', 'thePassword')
Run Code Online (Sandbox Code Playgroud)

看来您正在尝试增加 的模块声明Express,而且您非常接近。这应该可以解决问题:

// index.d.ts
import { User } from "./models/user";
declare module 'express' {
  interface Session {
    user: User;
    uuid: string;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 对我来说也不起作用。一旦我将导入语句添加到 tsd.d.ts 文件中,整个文件就会停止工作。(我在应用程序的其余部分中遇到了该文件中定义的错误。) (6认同)
  • 我有同样的问题。如果您在 .d.ts 中声明的模块中使用导入,则它可以工作: ```declare module 'myModule' {import { FancyClass } from 'fancyModule'; 导出类 MyClass 扩展 FancyClass {} }``` (6认同)