Kelas impor dalam file definisi (* d.ts)

96

Saya ingin memperluas pengetikan Sesi Ekspres untuk memungkinkan penggunaan data khusus saya dalam penyimpanan sesi. Saya memiliki objek req.session.useryang merupakan turunan dari kelas saya User:

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

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

Jadi saya membuat own.d.tsfile saya untuk menggabungkan definisi dengan jenis sesi ekspres yang ada:

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

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

Tapi itu tidak berfungsi sama sekali - VS Code dan tsc tidak melihatnya. Jadi saya membuat definisi tes dengan tipe sederhana:

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

Dan bidang uji bekerja dengan baik, jadi impor menyebabkan masalah.

Saya juga mencoba menambahkan /// <reference path='models/user.ts'/>sebagai gantinya impor tetapi tsc tidak melihat kelas Pengguna - bagaimana saya dapat menggunakan kelas saya sendiri di file * d.ts?

EDIT: Saya mengatur tsc untuk menghasilkan file definisi pada kompilasi dan sekarang saya memiliki user.d.ts saya:

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

Dan file pengetikan sendiri untuk memperluas Express Sesion:

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

Tapi masih tidak berfungsi saat pernyataan impor di atas. Ada ide?

Michał Lytek
sumber

Jawaban:

272

Setelah dua tahun pengembangan TypeScript, saya akhirnya berhasil memecahkan masalah ini.

Pada dasarnya, TypeScript memiliki dua jenis deklarasi tipe modul: "local" (modul normal) dan ambient (global). Jenis kedua memungkinkan untuk menulis deklarasi modul global yang digabungkan dengan deklarasi modul yang ada. Apa perbedaan antara file-file ini?

d.tsfile diperlakukan sebagai deklarasi modul ambient hanya jika tidak memiliki impor. Jika Anda menyediakan baris import, sekarang diperlakukan sebagai file modul normal, bukan yang global, jadi definisi modul tambahan tidak berfungsi.

Jadi itulah mengapa semua solusi yang kita diskusikan di sini tidak berfungsi. Tapi untungnya, sejak TS 2.9 kami dapat mengimpor tipe ke deklarasi modul global menggunakan import()sintaks:

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

Jadi garis import("./user").User;melakukan keajaiban dan sekarang semuanya berfungsi :)

Michał Lytek
sumber
4
Ini adalah cara yang tepat untuk melakukannya, setidaknya dengan versi skrip terbaru
Jefferson Tavares
1
Pendekatan ini adalah solusi ideal saat mendeklarasikan antarmuka yang memperluas modul global seperti objek Node process.
Teffen Ellis
1
Terima kasih, ini adalah satu-satunya jawaban yang jelas untuk memperbaiki masalah saya dengan perpanjangan Express Middleware!
Katsuke
Terima kasih, banyak membantu. Tidak ada cara lain untuk melakukan ini dalam proyek yang sudah ada. Lebih suka ini daripada memperluas kelas Request. Dziękuję bardzo.
Christophe Geers
1
Terima kasih @ Michał Lytek. Saya ingin tahu apakah ada referensi dokumentasi resmi untuk pendekatan ini?
Gena
3

MEMPERBARUI

Sejak skrip ketikan 2.9, Anda tampaknya dapat mengimpor tipe ke dalam modul global. Lihat jawaban yang diterima untuk informasi lebih lanjut.

JAWABAN ASLI

Saya pikir masalah yang Anda hadapi lebih banyak tentang menambah deklarasi modul kemudian mengetik kelas.

Pengeksporan baik-baik saja, karena Anda akan melihat jika Anda mencoba mengompilasi ini:

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

Sepertinya Anda mencoba menambah deklarasi modul Express, dan Anda sudah sangat dekat. Ini harus melakukan trik:

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

Namun, kebenaran kode ini tentu saja bergantung pada implementasi asli dari file deklarasi ekspres.

Pelle Jacobs
sumber
Jika saya pindah pernyataan impor dalam saya mendapatkan error: Import declarations in a namespace cannot reference a module.. Jika saya copy-paste kode Anda aku: Import or export declaration in an ambient module declaration cannot reference module through relative module name.. Dan jika saya mencoba menggunakan jalur non-relatif, saya tidak dapat menemukan file saya, jadi saya memindahkan folder deklarasi ke jalur penambahan iklan node_modules "declarations/models/user"tetapi tetap saja seluruh d.ts tidak berfungsi - tidak dapat melihat ekstensi sendiri dari sesi ekspres di intelisense atau tsc.
Michał Lytek
Saya tidak paham dengan kesalahan ini, maaf. Mungkin ada sesuatu yang berbeda dalam penyiapan Anda? Apakah ini untuk dikompilasi untuk Anda? gist.github.com/pellejacobs/498c997ebb8679ea90826177cf8a9bad .
Pelle Jacobs
Dengan cara ini berfungsi tetapi masih tidak berfungsi di aplikasi nyata. Saya memiliki objek permintaan ekspres dengan objek sesi dan memiliki tipe lain yang dideklarasikan - di namespace Express bukan modul 'express': github.com/DefinitelyTyped/DefinitelyTyped/blob/master/…
Michał Lytek
5
Itu juga tidak berhasil untuk saya. Setelah saya menambahkan pernyataan import ke file tsd.d.ts saya, seluruh file berhenti bekerja. (Saya mendapatkan kesalahan di sisa aplikasi saya untuk hal-hal yang ditentukan dalam file itu.)
Vern Jensen
5
Saya memiliki masalah yang sama. Ini berfungsi jika Anda menggunakan impor dalam modul yang dideklarasikan dalam .d.ts Anda: declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
zunder
2

Terima kasih atas jawaban dari Michał Lytek . Berikut adalah metode lain yang saya gunakan dalam proyek saya.

Kita dapat mengimpor Userdan menggunakannya kembali beberapa kali tanpa menulis di import("./user").Usermana-mana, dan bahkan memperpanjang atau mengekspornya kembali.

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

Selamat bersenang-senang :)

h00w
sumber
-1

Apakah tidak mungkin hanya mengikuti logika dengan express-session:

own.d.ts:

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

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

Yang utama 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;
});

Setidaknya ini tampaknya dapat dikompilasi tanpa masalah apa pun. Untuk kode lengkapnya, lihat https://github.com/masa67/so39040108

masa
sumber
1
Anda tidak boleh mengimpor file deklarasi, karena tsctidak akan mengkompilasinya. Mereka dimaksudkan untuk berada dalam kompilasi tetapi tidak dalam keluaran
Balint Csak