Typescript: Разрешить модулю реализовать интерфейс

Созданный на 10 авг. 2014  ·  34Комментарии  ·  Источник: microsoft/TypeScript

Было бы полезно, когда модуль может реализовать интерфейс с помощью ключевого слова implements . Синтаксис: module MyModule implements MyInterface { ... } .

Пример:

interface Showable {
    show(): void;
}
function addShowable(showable: Showable) {

}

// This works:
module Login {
    export function show() {
        document.getElementById('login').style.display = 'block';
    }
}
addShowable(Login);

// This doesn't work (yet?)
module Menu implements Showable {
    export function show() {
        document.getElementById('menu').style.display = 'block';
    }
}
addShowable(Menu);
Suggestion help wanted

Самый полезный комментарий

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

Например, Next.js сканирует ./pages/**/*.{ts,tsx} предмет модулей вашей страницы, генерируя маршруты на основе ваших имен файлов. Вы должны убедиться, что каждый модуль экспортирует нужные вещи ( NextPage в качестве экспорта по умолчанию и дополнительный PageConfig экспорт с именем config ):

import { NextPage, PageConfig } from 'next'

interface Props { userAgent?: string }

const Home: NextPage<Props> = ({ userAgent }) => (<main>...</main>)

Page.getInitialProps = async ({ req }) => {
  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
  return { userAgent }
}

export default Page

export const config: PageConfig = {
  api: { bodyParser: false }
}

Было бы неплохо, если бы вы могли вместо этого объявить экспортную форму всего модуля в одной строке в верхней части, например implements NextPageModule<Props> .

Другая мысль: было бы интересно, если бы был какой-то способ указать в конфигурации TypeScript, что все файлы, соответствующие определенному шаблону (например, ./pages/**/*.{ts,tsx} ), должны реализовывать определенную форму экспорта, чтобы модуль мог иметь свой тип экспорта - проверено просто потому, что, например, он находится в каталоге pages . Но я не уверен, есть ли у такого подхода прецедент, и это может сбить с толку.

Все 34 Комментарий

Как это будет работать с внешними модулями? Скорее всего, когда люди смогут использовать его для внутреннего, они также захотят использовать его для внешнего.

Это хороший вопрос. Я не знаю, какой синтаксис был бы лучшим, но вот несколько советов:

implements Showable; // I would prefer this one.
module implements Showable;
export implements Showable;

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

Утверждено. Мы предпочитаем синтаксис

export implements Showable;

и согласился, что это не нужно для файлов export = назначений.

Еще несколько вопросов:

  • Поскольку мы разрешаем модулям иметь типы на сайтах объявлений, не следует ли разрешать им иметь типы и на сайтах использования. например:
declare module "Module" implements Interface { }

import i : Interface = require("Module");
  • Что вы делаете с объединенными объявлениями, если вы применяете интерфейс к совокупности всех объявлений? а что будет, если они не совпадают по видимости?
    например:
module Foo {
    export interface IBar {
        (a:string): void;
    }

    export module Bar implements IBar {  // should this be an error?
        export interface Interface {}
    }    

    function Bar(a: string) : void { }  // not exported
}

var bar: Foo.IBar = Foo.Bar;

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

declare module "first" implements Foo { }
declare module "second"  {
  interface Bar { }
  export implements Bar; // this syntax is necessary, with the first syntax you can't reference Bar.  
}

Или Bar должен быть в области видимости в предложении орудий до открытия { ?

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

А для объединенных объявлений я бы сказал, что блок модуля, который содержит предложение реализации, должен реализовывать интерфейс. Это также предотвращает проблемы с видимостью.

Как это будет связано с № 2159? Пространство имен реализует интерфейс?

@jbondc Если бы у нас было это, это также применимо к пространствам имен. Вы должны думать о внутренних модулях и пространствах имен как о изоморфных.

Вы уверены, что хотите пойти по пути реализации, где «пространства имен» могут реализовывать интерфейсы?

Ого, это было одобрено довольно давно. @RyanCavanaugh , @DanielRosenwasser , @mhegazy, если у вас не возникнут какие-либо дополнительные мысли или настройки, я, вероятно, скоро это сделаю.

Я отказываюсь от своего прежнего скептицизма, я фактически отказался от новых структурных возможностей, которые это принесет.

В соответствии с этим, пожалуйста, рассмотрите возможность принудительного применения интерфейса агрегата интерфейса вместо только блока, который объявляет реализацию - Природа пространств имен / модулей должна быть распределена и содержать множество нетривиальных компонентов. Я хотел бы иметь возможность использовать это, но я определенно не хочу определять все мое пространство имен / модуль в одном файле. Почему бы в таком случае просто не использовать класс?

@ Elephant-Vessel Я не уверен, говорим ли мы о модулях, пространствах имен, пакетах, функциях или ...

@aluanhaddad Что ты имеешь в виду?

Я имею в виду, что в то время, когда началось это обсуждение, модуль не имел в виду то, что он означает сегодня. Теперь мы используем термин пространство имен для обозначения того, что описывается в OP как модуль, в то время как модуль приобрел более точное и несовместимое значение. Итак, когда вы говорите о нескольких файлах, участвующих в этой реализации, вы имеете в виду пространства имен или модули?

Я имею в виду пространства имен. Думаю, я просто хотел соответствовать истории этого потока, извините за то, что не вырвался :) Или когда я думаю об этом, может быть, у меня в голове был общий термин `` модуль '', описывающий единицу более высокого уровня, состоящую из набора из подкомпонентов, собранных для обеспечения определенных функций высокого уровня в системе. Но я не против просто использовать «пространства имен».

Поэтому я хочу иметь возможность описывать и накладывать ограничения и ожидания на [_generic modules_], которые могут содержать другие [_generic modules_] или классы, используя преимущества пространств имен структурных концепций в машинописном тексте.

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

Я был бы признателен за простой и понятный способ описания структуры системы более высокого порядка, без суеты. Желательно, чтобы единственной суетой были дополнительные ограничения направленной видимости. Это похоже на невозможность ссылки на _MySystem.ClientApplication_ из _MySystem.Infrastructure_, но отлично наоборот. Потом мы начинали куда-нибудь увлекательно.

@ Elephant-Vessel благодарит за разъяснения. Я согласен, что это было бы чрезвычайно ценно и что типы классов здесь не подходят. Я думаю, вы попали в точку, когда говорите об инстанциации, потому что пространства имен представляют вещи, которые концептуально являются одиночными на уровне библиотеки. Хотя это не может быть реализовано, было бы концептуально полезно иметь что-то, что не _просто_ множественные экземпляры.

Я согласен с @ Elephant-Vessel. Хотя TypeScript легко ошибочно принять за другую Java, где все ограничения выражены с помощью единой структуры классов, TS имеет гораздо более широкую концепцию «Shape», которая очень эффективна и устраняет семантический искажение. К сожалению, неспособность наложить ограничения на модуль, как правило, вынуждает разработчиков возвращаться к шаблону класса для вещей, которые гораздо лучше выразить как модуль.

Например, для модульного тестирования было бы очень полезно иметь возможность выражать некоторую «форму» (то есть ограничения) модулей, чтобы мы могли предоставить альтернативную реализацию для конкретного рабочего контекста. Теперь кажется, что единственный способ сделать это структурированным / проверенным способом - это вернуться к DI на основе классов (как la Spring) и сделать все классом (и, следовательно, создать экземпляр).

В любом случае, я перефразирую @ Elephant-Vessel, но если у меня есть хоть одно желание TS, то это будет именно это.

Есть какие-нибудь сведения об этой птице? У меня тоже есть эта проблема

уууууу, разве это не будет простой случай:

export {} as IFooBar;

что не так с этим синтаксисом? Думаю, синтаксис уже одобрен, возможно, как

export implements IFooBar

в любом случае с нетерпением жду этого

Это уже зачислено / приземлилось? это будет классная функция

Как мы можем добиться этого? Это невероятно мощно. Рад помочь!

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

export default {}

Полагаю, я могу просто сделать:

const x: MyInterface = {}
export default x;

это будет работать для большинства файлов TS, но проблема в том, что если вы сначала кодируете JS, а потом планируете перейти на TS, то это не так хорошо работает.

Еще одна вещь, о которой я подумал, как насчет реализуемых пространств имен? Что-то вроде:

export namespace Foo implements Bar {

}

Думаю, Bar будет _abstract_ пространством имен lol idk

Я видел, как этот вопрос поднимается так много раз, и я думаю, что все мы ищем только одну вещь:
Поддерживайте статические члены в интерфейсе.
Если бы это произошло, вы могли бы просто использовать класс со статическими членами и интерфейсом, что почти то же самое, что вы пытаетесь сделать здесь, верно?

В любом случае добавьте статическую поддержку к интерфейсам ИЛИ добавьте поддержку интерфейсов для модулей.

@shiapetel нах не нравится.

мы можем это сделать:

export default <T>{
  foo: Foo,
  bar: Bar
}

но это не то, что мы ищем. мы специально ищем:

export const foo : Foo = {};
export const bar : Bar = {};

но в настоящее время нет механизма, заставляющего модуль экспортировать foo и bar. И на самом деле нет никакого механизма, который заставил бы модуль экспортировать правильное значение по умолчанию.

Если интерфейсы поддерживают статические члены, вы можете использовать класс со статическим foo / bar, унаследованный от:
Интерфейс ILoveFooBar {
статический foo: FooType;
статическая панель: BarType;
}

Правильно?
Это то, что я имел в виду, думаю, это поможет в вашей ситуации - я знаю, что это определенно поможет в моей.

Статические элементы интерфейсов @shaipetel определенно могут быть полезны, но, возможно, не для этого

Эта проблема просто ждет, когда кто-то попытается ее реализовать?

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

Например, Next.js сканирует ./pages/**/*.{ts,tsx} предмет модулей вашей страницы, генерируя маршруты на основе ваших имен файлов. Вы должны убедиться, что каждый модуль экспортирует нужные вещи ( NextPage в качестве экспорта по умолчанию и дополнительный PageConfig экспорт с именем config ):

import { NextPage, PageConfig } from 'next'

interface Props { userAgent?: string }

const Home: NextPage<Props> = ({ userAgent }) => (<main>...</main>)

Page.getInitialProps = async ({ req }) => {
  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
  return { userAgent }
}

export default Page

export const config: PageConfig = {
  api: { bodyParser: false }
}

Было бы неплохо, если бы вы могли вместо этого объявить экспортную форму всего модуля в одной строке в верхней части, например implements NextPageModule<Props> .

Другая мысль: было бы интересно, если бы был какой-то способ указать в конфигурации TypeScript, что все файлы, соответствующие определенному шаблону (например, ./pages/**/*.{ts,tsx} ), должны реализовывать определенную форму экспорта, чтобы модуль мог иметь свой тип экспорта - проверено просто потому, что, например, он находится в каталоге pages . Но я не уверен, есть ли у такого подхода прецедент, и это может сбить с толку.

Я часто испытываю искушение создать синглтон-класс, когда мне нужен простой модуль, реализующий интерфейс. Любые советы, как лучше всего решить эту проблему?

@RyanCavanaugh @DanielRosenwasser
Я хочу поработать над этим вопросом. Не могли бы вы дать мне несколько советов по решению или где посмотреть?

Думая об этом с точки зрения 2020 года, мне интересно, если бы вместо export implements Showable мы повторно использовали type и разрешили export в качестве идентификатора? Сегодня это недопустимый синтаксис, поэтому маловероятно, что кто-то наступит на существующую кодовую базу.

Тогда получаем синтаксис импорта:

// Can re-use the import syntax
type export = import("webpack").Config

Тогда декларации легко написать:

// Can use normal literals
type export = { test: () => string, description: string }

// Generics are easy
type export = (props: any) => React.SFC<MyCustomModule>

Также стоит подумать, каким должен быть эквивалент JSDoc, возможно:

/** <strong i="17">@typedef</strong> {import ("webpack").Config} export */

В ^ есть несколько примечаний - одна интересная вещь, которая пришла к выводу из встречи, заключалась в том, что мы могли бы создать более общий инструмент, для которого это вариант использования, а не единственный, что он делает.

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

type assert is import("webpack").Config

const path = require('path');

export default {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

Если отсутствие цели означает применение ее на верхнем уровне. Это можно использовать для обеспечения контекстной типизации (например, вы получите автозаполнение в export default { en|

Но также может быть полезно при проверке ваших собственных типов:

import {someFunction} from "./example"

type assert ReturnType<typeof someFunction> is string

Также стоит подумать, каким должен быть эквивалент JSDoc, возможно:
js /** <strong i="7">@typedef</strong> {import ("webpack").Config} export */

Я бы подумал, что @module будет эквивалентом JSDoc. В верхней части файла должны быть:
js /** <strong i="12">@module</strong> {import("webpack").Config} moduleName */

См. Https://jsdoc.app/tags-module.html

Storybook v6 изменился на подход, основанный на структурированных модулях, которые они назвали Component Story Format . Ожидается, что все модули .stories.js/ts в базе кода будут включать экспорт по умолчанию с типом Meta .

Отсутствие возможности выразить это ожидание в глобальном масштабе в сочетании с существующим недостатком в типизации экспорта по умолчанию делает использование Storybook v6 с TypeScript гораздо менее плавным, чем могло бы быть.

Чтобы добавить к точкам @jonrimmer , экспорт default который имеет определенную type которая копирует module , приведет к проблемам с встряхиванием дерева.

У Webpack нет проблем с раскачиванием дерева import * as Foo . Но когда вы пытаетесь сделать то же самое с export default const = {} или export default class ModuleName { со всеми статическими членами, неиспользуемый импорт не удаляется.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги