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

时间:2016-08-19 13:18:46

标签: 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) : "";
    }
}

所以我创建了我的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语句在顶部时仍然无效。有什么想法吗?

6 个答案:

答案 0 :(得分:58)

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

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

仅当

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

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

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

所以这行import("./user").User;发挥了神奇作用,现在一切正常:)

答案 1 :(得分:12)

为了完整起见:

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

如果你这样做: (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 块中(谈到这个例子,在其他没有声明模块的例子中,你可以在其他地方内联导入)

  • 要做到这一点,您不能像这样使用常规导入

    declare module B {
      import A from '../A'
      const a: A;
    }
    

    因为在当前的实现中,这个导入模块的解析规则令人困惑,因此 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;
    }
    

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

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

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

    如已接受的答案 (https://stackoverflow.com/a/51114250/2054671) 中所述

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

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

    但请记住,全局增强只能在模块中而不是环境声明中才能使用,因此只有将其导入到使用者文件中才有效,如@masa 的回答 (https://stackoverflow.com/a/55721549/2054671) 中所述


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

  • 您可以使用 /// <reference types="../../a" /> 指令

    // ambientA.d.ts
    interface A {
      t: string
    }
    
    // ambientB.d.ts
    /// <reference types="../ambientA.d.ts" />
    declare module B {
      const a: A;
      export { a };
    }
    

其他相关答案的链接:

答案 2 :(得分:3)

感谢the answer from 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 }
}

玩得开心:)

答案 3 :(得分:1)

<强>更新

从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;
  }
}

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

答案 4 :(得分:0)

是否不可能仅遵循mse = Custom_loss(y_real, y_pred, df_for_loss,price) def Custom_loss(y_true, y_pred, df_for_loss, price, sample_weight=None, multioutput='uniform_average'): df_for_loss['y_pred']=y_pred df_for_loss['agg_closing_stock'] = np.where(df_for_loss.agg_closing_stock> 0, (price/2)*df_for_loss.agg_closing_stock, df_for_loss.agg_closing_stock) df_for_loss['agg_closing_stock'] = np.where(df_for_loss.agg_closing_stock== 0, price*df_for_loss.y_pred, df_for_loss.agg_closing_stock) df_for_loss.agg_closing_stock= df_for_loss.agg_closing_stock.astype('float32', raise_on_error = False) y_pred= y_pred.astype('float32', raise_on_error = False) data=tf.convert_to_tensor(df_for_loss.agg_closing_stock) y_pred=K.tf.math.multiply(y_pred,data) output_errors = np.average((y_true - y_pred) ** 2, axis=0, weights=sample_weight) if isinstance(multioutput, string_types): if multioutput == 'raw_values': return output_errors elif multioutput == 'uniform_average': # pass None as weights to np.average: uniform mean multioutput = None return np.average(output_errors, weights=multioutput) 的逻辑:

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

至少似乎可以毫无问题地进行编译。有关完整的代码,请参见https://github.com/masa67/so39040108

答案 5 :(得分:0)

请看这里:

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