Импортировать класс в файл определения (* d.ts)


96

Я хочу расширить типизацию экспресс-сеанса, чтобы разрешить использование моих пользовательских данных в хранилище сеанса. У меня есть объект, 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'/>вместо этого import, но 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;
    }
}

Но все еще не работает, когда оператор импорта сверху. Любые идеи?

Ответы:


271

После двух лет разработки TypeScript мне наконец удалось решить эту проблему.

По сути, TypeScript имеет два типа объявления типов модулей: «локальные» (обычные модули) и внешние (глобальные). Второй тип позволяет записывать объявление глобальных модулей, которые объединяются с объявлением существующих модулей. В чем разница между этими файлами?

d.tsфайлы рассматриваются как объявления внешнего модуля, только если они не имеют импорта. Если вы предоставляете строку импорта, она теперь обрабатывается как обычный файл модуля, а не как глобальный, поэтому определение дополнительных модулей не работает.

Вот почему все решения, которые мы здесь обсуждали, не работают. Но, к счастью, начиная с TS 2.9 мы можем импортировать типы в объявление глобальных модулей, используя import()синтаксис:

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

Итак, линия import("./user").User;творит чудеса, и теперь все работает :)


4
Это правильный способ сделать это, по крайней мере, с последними версиями машинописного текста
Джефферсон Таварес

1
Этот подход является идеальным решением при объявлении интерфейсов, расширяющих глобальные модули, такие как processобъект Node .
Teffen Ellis

1
Спасибо, это был единственный четкий ответ на решение моих проблем с расширением Express Middleware!
Katsuke

Спасибо, очень помогло. Не было другого способа сделать это в существующем проекте. Предпочитайте это расширению класса Request. Dziękuję bardzo.
Christophe Geers

1
Спасибо @ Michał Lytek. Мне интересно, есть ли официальная ссылка на документацию для этого подхода?
Гена

3

ОБНОВИТЬ

Начиная с машинописного текста 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;
  }
}

Однако правильность этого кода, конечно, зависит от исходной реализации файла экспресс-декларации.


Если переместить оператор импорта внутри я получаю сообщение об ошибке: Import declarations in a namespace cannot reference a module.. Если я копировать-вставить код , который я получил: Import or export declaration in an ambient module declaration cannot reference module through relative module name.. И если я попытаюсь использовать не относительный путь, я не могу найти свой файл, поэтому я переместил папку объявлений в путь добавления объявления node_modules, "declarations/models/user"но все же весь d.ts не работает - не вижу собственного расширения экспресс-сеанса в intelisense или tsc.
Michał Lytek

Я не знаком с этими ошибками, извините. Может в вашей настройке что-то другое есть? Это для вас компилируется? gist.github.com/pellejacobs/498c997ebb8679ea90826177cf8a9bad .
Пелле Джейкобс,

Таким образом, это работает, но все еще не работает в реальном приложении. У меня есть объект экспресс-запроса с объектом сеанса, и у него объявлен другой тип - в пространстве имен Express, а не модуль 'express': github.com/DefinentyTyped/DefinentyTyped/blob/master/…
Michał Lytek

5
У меня тоже не работает. Как только я добавляю операторы импорта в свой файл tsd.d.ts, весь файл перестает работать. (Я получаю ошибки в остальной части моего приложения для вещей, определенных в этом файле.)
Верн Дженсен,

5
У меня такая же проблема. Это работает, если вы используете импорт в объявленном модуле в вашем .d.ts: declare module 'myModule' {import { FancyClass } from 'fancyModule'; export class MyClass extends FancyClass {} }
zunder

2

Благодаря ответу Михала Литека . Вот еще один метод, который я использовал в своем проекте.

Мы можем импортировать 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 }
}

Радоваться, веселиться :)


-1

Разве невозможно просто следовать логике 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:

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

По крайней мере, похоже, что это компилируется без каких-либо проблем. Полный код см. На https://github.com/masa67/so39040108.


1
Вы не должны импортировать файлы декларации, потому tscчто не скомпилируете их. Они предназначены для компиляции, но не для вывода
Балинт Чак
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.