typescript - Importera klass i definitionsfil (*d.ts)


Jag vill utöka Express Session-typningarna för att tillåta användning av mina egna data i sessionslagring. Jag har ett objekt req.session.user som är en instans av min klass User:

export class User {
    public login: string;
    public hashedPassword: string;

    constructor(login?: string, password?: string) {
        this.login = login || "" ;
        this.hashedPassword = password ? UserHelper.hashPassword(password) : "";
    }
}

Så jag skapade min own.d.ts-fil för att slå ihop definitionen med befintliga Express-sessiontypningar:

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

declare module Express {
    export interface Session {
        user: User;
    }
}

Men det fungerar inte alls - VS Code och tsc ser det inte. Så jag skapade ett testdefinition med enkel typ:

declare module Express {
    export interface Session {
        test: string;
    }
}

Och testfältet fungerar okej, så importen orsakar problem.

Jag försökte också lägga till /// <reference path='models/user.ts'/> istället för import men tsc såg inte User-klassen - hur kan jag använda min egen klass i en *d.ts-fil?

REDIGERA: Jag har ställt in tsc för att generera definitionsfiler vid kompilering och nu har jag min user.d.ts:

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

Och den egna typeringsfilen för att utöka Express Session:

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

Men det fungerar fortfarande inte när import-satsen är överst. Några idéer?

Svar #1

Efter två års TypeScript-utveckling har jag äntligen lyckats lösa det här problemet.

I princip har TypeScript två typer av modultypdeklarationer: "lokal" (vanliga moduler) och ambient (global). Den andra typen tillåter att skriva globala moduldeklarationer som slås ihop med befintliga moduldeklarationer. Vad är skillnaderna mellan dessa filer?

d.ts-filer behandlas som enbart ambienta moduldeklarationer om de inte har några imports. Om du anger en importrad behandlas den nu som en vanlig modulfiler, inte den globala, så att förstärkning av modulers definitioner inte fungerar.

Så det är därför alla lösningarna vi diskuterade här inte fungerar. Men lyckligtvis, sedan TS 2.9 kan vi importera typer i globala moduldeklarationer med import()-syntax:

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

Så raden import("./user").User; gör magin och nu fungerar allt :)

Svar #2

Tack för svaret från Michał Lytek. Här är en annan metod som jag använde i mitt projekt.

Vi kan importera User och återanvända det flera gånger utan att skriva import("./user").User överallt, och till och med implementera det eller återexportera det.

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

Ha kul :)

Svar #3

För fullständighetens skull:

  • om du har en ambient moduldeklaration (dvs. utan någon toppnivå import/export) är den tillgänglig globalt utan att behöva importera den uttryckligen någonstans, men om du har en moduldeklaration måste du importera den i konsumentfilen.
  • om du vill importera en befintlig typ (som exporteras från någon annan fil) i din omgivande moduldeklaration, kan du inte göra det med en import på högsta nivå (för då skulle det inte förbli en omgivande deklaration).

Så om du gör detta: (https://stackoverflow.com/a/39132319/2054671)

// index.d.ts
import { User } from "./models/user";
declare module 'express' {
  interface Session {
    user: User;
    uuid: string;
  }
}

detta kommer att utöka den befintliga 'express'-modulen med detta nya gränssnitt. https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

men för att använda detta skulle du behöva importera detta i din konsumentfil, det kommer inte att vara tillgängligt globalt som standard som en omgivande deklaration eftersom det inte längre är en omgivande deklaration

  • så, för att importera en befintlig typ som exporterats från en annan fil, måste du importera den inuti declare-blocket (talar om detta exempel, i andra exempel där du inte deklarerar en modul, kan du importera inline på andra ställen)

  • för att göra detta kan du inte använda en vanlig import som denna

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

    eftersom i nuvarande implementation är reglerna för upplösning av denna importerade modul förvirrande, och därför tillåter inte ts detta. Detta är orsaken till felet Import or export declaration in an ambient module declaration cannot reference module through relative module name. (Jag kan inte hitta länken till den relevanta github-frågan, om någon hittar den, vänligen redigera detta svar och nämn den. https://github.com/microsoft/TypeScript/issues/1720)

    Observera att du fortfarande kan göra något som detta:

    declare module B {
      import React from 'react';
      const a: A;
    }
    

    eftersom detta är en absolut sökvägsimport och inte en relativ sökvägsimport.

  • så det enda sättet att korrekt göra detta i en ambient-modul är att använda syntaxen för dynamisk import (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#import-types)

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

    som nämnts i det accepterade svaret (https://stackoverflow.com/a/51114250/2054671)

  • du kan också göra en global förstärkning med något som detta:

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

    men kom ihåg att global förstärkning endast är möjlig i en modul och inte i en ambient deklaration, så detta skulle bara fungera om du importerar det i konsumentfilen, som nämnts i @masa:s svar (https://stackoverflow.com/a/55721549/2054671)


Alla ovanstående punkter gäller för att importera en modul som exporteras från någon annanstans, i dina ambienta moduler, men hur är det med att importera en ambient modul i en annan ambient modul? (Detta är användbart om du vill använda en befintlig ambient deklaration i din egen ambienta moduldeklaration och säkerställa att dessa ambianta typer också är synliga för användaren av din ambienta modul)

  • du kan använda /// <reference types="../../a" />-direktivet

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

Länkar till andra relevanta svar:

  • Ambient deklaration med en importerad typ i TypeScript

Relaterade artiklar