Как использовать экспортированные константные типы файла d.ts, если реализация библиотеки не интегрирована с проектом TS? - PullRequest
16 голосов
/ 21 апреля 2019

DefinitiveTyped имеет определения типов для многих библиотек, но достаточно часто я не могу найти хороший способ использовать их, когда реализация Javascript отделена от Typescript, например, когда библиотека назначает себя свойству окна через

<script src="https://example.com/library.js">

, и когда я управляю пакетом JS, это другой отдельный скрипт. (Даже если объединить все вместе , включая , библиотека является стандартным и надежным методом, предположим, ради вопроса, что у меня нет возможности импортировать библиотеку в мой проект TS.) Например, скажем, я нахожу красивый файл определения для библиотеки с именем myLib:

// my-lib.d.ts
export const doThing1: () => number;
export const doThing2: () => string;
export const version: string;
export interface AnInterface {
  foo: string;
}
export as namespace myLib;

В JS я могу использовать myLib, вызывая window.myLib.doThing1() и window.myLib.doThing2(). Как я могу импортировать форму всего объекта window.myLib, чтобы я мог объявить его как свойство window? Я вижу, что могу импортировать экспортированные интерфейсы , например:

// index.ts
import { AnInterface } from './my-lib';
const something: AnInterface = { foo: 'foo' };
console.log(something.foo);

Это работает, но я хочу получить доступ к форме фактического объекта библиотеки и значений его свойств (функций и строк и т. Д.), А не только к интерфейсам. Если я сделаю

import * as myLib from './my-lib';

тогда идентификатор myLib становится пространством имен, из которого я могу ссылаться на экспортированные интерфейсы, но, как и выше, у меня все еще нет доступа к фигурам export const и export function из my-lib.d.ts. (И, конечно, попытка использовать импортированное пространство имен для объявления объекта библиотеки не работает: Cannot use namespace 'myLib' as a type. Даже если бы я мог это сделать, это не обязательно было бы безопасно, поскольку библиотека, упакованная для браузера, вполне может быть немного отличается от объекта экспорта Node библиотеки)

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

// index.ts
declare global {
  interface Window {
    myLib: {
      doThing1: () => number;
      doThing2: () => string;
      version: string;
    };
  }
}

Но это грязно, отнимает много времени и, конечно, не правильный способ сделать что-то подобное. Когда я сталкиваюсь с такой ситуацией, я бы вроде смог бы сделать что-то короткое и элегантное, например:

// index.ts
import myLibObjectInterface from './my-lib.d.ts'; // this line is not correct
declare global {
  interface Window {
    myLib: myLibObjectInterface
  }
}

Некоторые файлы определений включают интерфейс для библиотечного объекта, например jQuery, который:

// index.d.ts
/// <reference path="JQuery.d.ts" />

// jQuery.d.ts
interface JQuery<TElement = HTMLElement> extends Iterable<TElement> {
  // lots and lots of definitions

Тогда все просто отлично - я могу просто использовать interface Window { $: jQuery }, но многие библиотеки, изначально не созданные для использования браузером, не предлагают такой интерфейс.

Как уже упоминалось ранее, наилучшим решением будет интеграция библиотеки с проектом TS, позволяющая редактировать и использовать библиотеку и ее типы import и использовать ее без суеты, но если это невозможно, сделайте У меня остались хорошие варианты? Я мог бы изучить свойства реального библиотечного объекта и добавить интерфейс к файлу определения, который включает в себя все такие свойства и их типы, но при этом необходимо изменить полуканонический файл (ы) определения источника, принятый DT и используемый всеми остальными. неправильно. Я бы предпочел иметь возможность импортировать формы экспорта файла определения и создавать из них интерфейс без изменения исходного файла, но это может оказаться невозможным.

Есть ли более элегантное решение, или файлы определений, с которыми я столкнулся, попросту недостаточно подходят для моей цели и, следовательно, должны быть изменены?

Ответы [ 2 ]

8 голосов
/ 23 апреля 2019

Если модуль имеет export as namespace myLib, то модуль уже экспортирует библиотеку как глобальный объект. Так что вы можете просто использовать библиотеку как:

let a:myLib.AnInterface;
let b =  myLib.doThing1();

Это верно, если файл, в котором вы используете библиотеку, не является самим модулем (т. Е. Он не содержит import и * export операторов).

export {} // module now
let a:myLib.AnInterface; // Types are still ok without the import
let b =  myLib.doThing1(); // Expressions are not ok, ERR: 'myLib' refers to a UMD global, but the current file is a module. Consider adding an import instead.ts(2686)

Вы можете добавить к Window свойство того же типа, что и тип библиотеки, используя тип импорта (добавлен в версии 2.9, в том числе)

// myLibGlobal.d.ts
// must not be a module, must not contain import/ export 
interface Window {
    myLib: typeof import('./myLib') // lib name here
}


//usage.ts
export {} // module
let a:myLib.AnInterface; // Types are still ok without the import (if we have the export as namespace
let b =  window.myLib.doThing1(); // acces through window ok now

Редактировать

Очевидно, команда Typescript действительно работала над чем-то для этой самой проблемы. Как вы можете прочитать в этом PR , следующая версия машинописи будет включать флаг allowUmdGlobalAccess. Этот флаг разрешает доступ к глобальным переменным модуля UMD из модулей. Если для этого флага установлено значение true, этот код будет действительным:

export {} // module now
let a:myLib.AnInterface; // Types are still ok without the import
let b =  myLib.doThing1(); // ok, on typescript@3.5.0-dev.20190425

Это означает, что вы можете просто получить доступ к экспорту модуля без необходимости использования окна. Это будет работать, если глобальный экспорт совместим с браузером, как я и ожидал.

3 голосов
/ 23 апреля 2019

С чем вы имеете дело

когда библиотека назначает себя свойству окна

Это называется UMD-пакетом . Это те, которые используются при добавлении ссылки внутри тега <script /> в документе, и они присоединяются к глобальной области видимости.

Пакеты UMD не должны использоваться таким образом - их также можно использовать как модули, используя оператор import (или require).

TypeScript поддерживает оба варианта использования.

Как набирать пакеты UMD

declare namespace Foo {
  export const bar: string;
  export type MeaningOfLife = number;
}

export as namespace Foo;
export = Foo;

Это определение сообщает TypeScript, что:

  • если файл-потребитель представляет собой скрипт , то в глобальной области видимости есть переменная bar
  • если файл-потребитель является модулем , то вы можете импортировать bar, используя именованный импорт, или импортировать все пространство имен, используя импорт с подстановочными знаками (*).

В чем разница между script и module ?

Сценарий будет частью JavaScript, работающего внутри тега <script /> в вашем HTML-документе. Он может быть встроен или загружен из файла. Именно так JavaScript всегда использовался в браузере.

A module - это файл JavaScript (TypeScript), в котором есть хотя бы один оператор import или export. Они являются частью стандарта ECMAScript и не поддерживаются повсеместно. Обычно вы создаете модулей в своем проекте и позволяете такому пакету, как Webpack, создавать пакет для вашего приложения.

Потребление пакетов UMD

Сценарий, переменные и типы используются путем доступа к глобальному пространству имен Foo (так же, как jQuery и $):

const bar = Foo.bar;
const meaningOfLife: Foo.MeaningOfLife = 42;

Сценарий, тип bar импортируется с использованием import type синтаксис:

const baz: typeof import ('foo').bar = 'hello';

Модуль, переменная bar импортируется с использованием именованного импорта.

import { bar } from 'foo';

bar.toUpperCase();

Модуль, весь пакет импортируется как пространство имен:

import * as foo from 'foo';

foo.bar.toUpperCase();

Глобальная область действия против окна

Если такая библиотека напечатана правильно, вам как потребителю не нужно ничего делать, чтобы она работала. Он будет доступен в вашей глобальной области автоматически, и никакого дополнения для Window не потребуется.

Однако, если вы хотите явно прикрепить содержимое вашей библиотеки к window, вы также можете сделать это:

declare global {
  interface Window {
    Foo: typeof import('foo');
  }
}

window.Foo.bar;
...