Importuj klasę w pliku definicji (* d.ts)

104

Chcę rozszerzyć zakres typów Express Session, aby umożliwić używanie moich danych niestandardowych w pamięci sesji. Mam obiekt req.session.userbędący instancją mojej klasy User:

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

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

Utworzyłem więc mój own.d.tsplik, aby scalić definicję z istniejącymi typami sesji ekspresowych:

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

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

Ale to w ogóle nie działa - VS Code i tsc go nie widzą. Utworzyłem więc definicję testu z prostym typem:

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

Pole testowe działa dobrze, więc problem powoduje import.

Próbowałem też /// <reference path='models/user.ts'/>zamiast tego dodać import, ale tsc nie widział klasy użytkownika - jak mogę użyć własnej klasy w pliku * d.ts?

EDYCJA: Ustawiłem tsc na generowanie plików definicji podczas kompilacji i teraz mam mojego user.d.ts:

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

Oraz własny plik do wpisywania do rozszerzenia Express Sesion:

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

Ale nadal nie działa, gdy instrukcja import na górze. Jakieś pomysły?

Michał Lytek
źródło

Odpowiedzi:

295

Po dwóch latach programowania TypeScript w końcu udało mi się rozwiązać ten problem.

Zasadniczo, TypeScript ma dwa rodzaje deklaracji typów modułów: „lokalne” (zwykłe moduły) i otoczenie (globalne). Drugi rodzaj pozwala na zapisanie globalnych deklaracji modułów, które są scalane z istniejącymi deklaracjami modułów. Jakie są różnice między tymi plikami?

d.tspliki są traktowane jako deklaracje modułu otoczenia tylko wtedy, gdy nie mają żadnych importów. Jeśli podasz wiersz importu, jest on teraz traktowany jako normalny plik modułu, a nie globalny, więc rozszerzanie definicji modułów nie działa.

Dlatego wszystkie omówione tutaj rozwiązania nie działają. Ale na szczęście od TS 2.9 jesteśmy w stanie importować typy do globalnej deklaracji modułów za pomocą import()składni:

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

Więc linia import("./user").User;robi magię i teraz wszystko działa :)

Michał Lytek
źródło
4
To jest właściwy sposób, przynajmniej z ostatnimi wersjami maszynopisu
Jefferson Tavares
1
Takie podejście jest idealnym rozwiązaniem w przypadku deklarowania interfejsów rozszerzających moduły globalne, takie jak processobiekt Node .
Teffen Ellis
2
Dzięki, to była jedyna jasna odpowiedź na rozwiązanie moich problemów z rozszerzaniem Express Middleware!
Katsuke
2
Dziękuję @ Michał Lytek. Zastanawiam się, czy istnieje oficjalna dokumentacja dotycząca tego podejścia?
Gena
5

AKTUALIZACJA

Od wersji 2.9 maszynopisu wydaje się, że możesz importować typy do modułów globalnych. Zobacz zaakceptowaną odpowiedź, aby uzyskać więcej informacji.

ORYGINALNA ODPOWIEDŹ

Myślę, że problem, z którym się zmagasz, polega bardziej na rozszerzaniu deklaracji modułów niż na typowaniu klas.

Eksportowanie jest w porządku, jak zauważysz, próbując skompilować to:

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

Wygląda na to, że próbujesz rozszerzyć deklarację modułu Expressi jesteś naprawdę blisko. To powinno załatwić sprawę:

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

Jednak poprawność tego kodu zależy oczywiście od oryginalnej implementacji pliku deklaracji ekspresowej.

Pelle Jacobs
źródło
Jeśli przenieść instrukcji import wewnątrz otrzymuję błąd: Import declarations in a namespace cannot reference a module.. Gdybym kopiuj-wklej kod mam: Import or export declaration in an ambient module declaration cannot reference module through relative module name.. A jeśli spróbuję użyć ścieżki względnej, nie mogę zlokalizować mojego pliku, więc przeniosłem folder deklaracji do ścieżki dodawania reklamy node_modules, "declarations/models/user"ale nadal cały d.ts nie działa - nie widzę własnego rozszerzenia sesji ekspresowej w intelisense lub tsc.
Michał Lytek
Nie znam tych błędów, przepraszam. Może w Twojej konfiguracji jest coś innego? Czy to skompilować dla Ciebie? gist.github.com/pellejacobs/498c997ebb8679ea90826177cf8a9bad .
Pelle Jacobs
W ten sposób działa, ale nadal nie działa w prawdziwej aplikacji. Mam tam obiekt express request z obiektem sesji i ma zadeklarowany inny typ - w przestrzeni nazw Express nie moduł 'express': github.com/DefinitelyTyped/DefinitelyTyped/blob/master/ ...
Michał Lytek
5
Na mnie też nie działa. Po dodaniu instrukcji importu do mojego pliku tsd.d.ts cały plik przestaje działać. (W pozostałej części mojej aplikacji pojawiają się błędy dotyczące elementów zdefiniowanych w tym pliku.)
Vern Jensen,
5
Miałem ten sam problem. Działa, jeśli użyjesz importu w zadeklarowanym module w swoim .d.ts: declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
zunder
4

Dzięki odpowiedzi Michała Lytka . Oto inna metoda, której użyłem w moim projekcie.

Możemy go importować Useri używać wielokrotnie bez zapisywania w import("./user").Userdowolnym miejscu, a nawet rozszerzać go lub ponownie eksportować .

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

Baw się dobrze :)

h00w
źródło
0

Czy nie można po prostu podążać za logiką express-session:

own.d.ts:

import express = require('express');
import { User } from "../models/user";

declare global {
    namespace Express {
        interface Session {
            user: User;
            uuid: string;
        }
    }
}

Głównie 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;
});

Przynajmniej wydaje się, że kompiluje się to bez żadnych problemów. Pełny kod znajduje się na https://github.com/masa67/so39040108

masa
źródło
1
Nie możesz importować plików deklaracji, ponieważ tscich nie skompilujesz. Mają być w kompilacji, ale nie na wyjściu
Balint Csak