typescript - Importer klasse i definisjonsfil (*d.ts)


Jeg vil utvide Express Session-typene for å tillate bruk av mine egne data i sesjonslagring. Jeg har et objekt req.session.user som er en instans av klassen min 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å jeg opprettet min egen own.d.ts-fil for å slå sammen definisjonen med eksisterende Express-session-typinger:

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

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

Men det fungerer ikke i det hele tatt - VS Code og tsc ser det ikke. Så jeg laget en testdefinisjon med en enkel type:

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

Og testfeltet fungerer greit, så importen forårsaker problemer.

Jeg prøvde også å legge til /// <reference path='models/user.ts'/> i stedet for import, men tsc så ikke User-klassen - hvordan kan jeg bruke min egen klasse i en *d.ts-fil?

REDIGER: Jeg har satt tsc til å generere definisjonsfiler ved kompilering, og nå har jeg min user.d.ts:

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

Og den egen typingsfilen for å utvide Express Session:

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

Men det fungerer fortsatt ikke når import-setningen er øverst. Noen ideer?

Svar #1

Etter to år med TypeScript-utvikling har jeg endelig klart å løse dette problemet.

I hovedsak har TypeScript to typer moduldeklarasjoner: "lokale" (vanlige moduler) og ambient (globale). Den andre typen gjør det mulig å skrive globale moduldeklarasjoner som blir slått sammen med eksisterende moduldeklarasjoner. Hva er forskjellene mellom disse filene?

Filer d.ts behandles kun som ambient moduldeklarasjoner hvis de ikke har noen imports. Hvis du legger til en import-linje, behandles den nå som en vanlig modulfil, ikke den globale, så utvidelse av moduldefinisjoner fungerer ikke.

Så det er derfor alle løsningene vi diskuterte her ikke fungerer. Men heldigvis, siden TS 2.9 kan vi importere typer inn i globale moduler deklarasjon ved å bruke import()-syntaksen:

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

Så linjen import("./user").User; gjør magien, og nå fungerer alt :)

Svar #2

Takk for svaret fra Michał Lytek. Her er en annen metode jeg brukte i prosjektet mitt.

Vi kan importere User og gjenbruke det flere ganger uten å skrive import("./user").User overalt, og til og med implementere det eller re-eksportere 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 det gøy :)

Svar #3

For fullstendighetens skyld:

  • Hvis du har en ambient moduldeklarasjon (dvs. uten noen toppnivå import/eksport), er den tilgjengelig globalt uten at du trenger å importere den eksplisitt noe sted, men hvis du har en moduldeklarasjon, må du importere den i filen som bruker den.
  • hvis du vil importere en eksisterende type (som eksporteres fra en annen fil) i din omgivende moduldeklarasjon, kan du ikke gjøre det med en toppnivå import (fordi da ville det ikke forbli en omgivende deklarasjon).

Så hvis du gjør dette: (https://stackoverflow.com/a/39132319/2054671)

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

dette vil utvide den eksisterende 'express'-modulen med dette nye grensesnittet. https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

men for å bruke dette må du importere det i forbrukerfilen din, det vil ikke være tilgjengelig som standard globalt slik som en ambient-deklarasjon, siden det ikke lenger er en ambient-deklarasjon

  • så, for å importere en eksisterende type som er eksportert fra en annen fil, må du importere den inne i declare-blokken (når vi snakker om dette eksempelet, i andre eksempler der du ikke erklærer en modul, kan du importere inline på andre steder)

  • for å gjøre dette, kan du ikke bruke en vanlig import som dette

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

    fordi i den nåværende implementeringen er reglene for oppløsning av denne importerte modulen forvirrende, og derfor tillater ikke ts dette. Dette er årsaken til feilen Import or export declaration in an ambient module declaration cannot reference module through relative module name. (Jeg klarer ikke å finne lenken til den relevante GitHub-issuen, hvis noen finner den, vennligst rediger dette svaret og nevnt det. https://github.com/microsoft/TypeScript/issues/1720)

    Vennligst merk, du kan fortsatt gjøre noe slik som dette:

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

    fordi dette er en absolutt sti-import og ikke en relativ sti-import.

  • så den eneste måten å gjøre dette riktig i en ambient-modul på er å bruke syntaksen for 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 nevnt i det aksepterte svaret (https://stackoverflow.com/a/51114250/2054671)

  • du kan også gjøre en global forbedring med noe som dette:

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

    men husk at global augmentering bare er mulig i en modul, ikke i en ambient deklarasjon, så dette vil bare fungere hvis du importerer det i forbrukerfilen, som nevnt i @masa sitt svar (https://stackoverflow.com/a/55721549/2054671)


Alle de ovennevnte punktene gjelder for å importere en modul som eksporteres fra et annet sted, i dine ambient-moduler, men hva med å importere en ambient-modul i en annen ambient-modul? (Dette er nyttig hvis du ønsker å bruke en eksisterende ambient-deklarasjon i din egen ambient-modul-deklarasjon og sørge for at disse ambient-typene også er synlige for brukeren av din ambient-modul)

  • du kan bruke /// <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 };
    }
    

Lenker til andre relevante svar:

  • Ambient deklarasjon med en importert type i TypeScript

Relaterte artikler