Typescript: Izinkan modul untuk mengimplementasikan antarmuka

Dibuat pada 10 Agu 2014  ·  34Komentar  ·  Sumber: microsoft/TypeScript

Akan berguna jika modul dapat mengimplementasikan antarmuka menggunakan kata kunci implements . Sintaks: module MyModule implements MyInterface { ... } .

Contoh:

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

Komentar yang paling membantu

Salah satu kasus penggunaan adalah untuk kerangka kerja dan alat yang memindai direktori untuk modul saat memulai aplikasi, mengharapkan semua modul tersebut mengekspor bentuk tertentu.

Misalnya, Next.js memindai ./pages/**/*.{ts,tsx} untuk modul halaman Anda, menghasilkan rute berdasarkan nama file Anda. Terserah Anda untuk memastikan setiap modul mengekspor hal yang benar ( NextPage sebagai ekspor default, dan opsional PageConfig export bernama 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 }
}

Alangkah baiknya jika Anda dapat mendeklarasikan bentuk ekspor seluruh modul dalam satu baris di dekat bagian atas, seperti implements NextPageModule<Props> .

Pemikiran lain: akan menarik jika ada beberapa cara untuk menentukan dalam konfigurasi TypeScript bahwa semua file yang cocok dengan pola tertentu (seperti ./pages/**/*.{ts,tsx} ) harus menerapkan bentuk ekspor tertentu, sehingga modul dapat memiliki jenis ekspornya- diperiksa murni karena terletak di dalam direktori pages misalnya. Tetapi saya tidak yakin apakah ada preseden untuk pendekatan ini, dan itu mungkin membingungkan.

Semua 34 komentar

Bagaimana ini bekerja dengan modul eksternal? Kemungkinan sekali orang dapat menggunakannya untuk internal, mereka juga ingin menggunakannya dengan eksternal.

Itu pertanyaan yang bagus. Saya tidak tahu sintaks mana yang terbaik, tetapi berikut beberapa saran:

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

Ini hanya diperbolehkan pada modul eksternal yang tidak menggunakan tugas ekspor, karena jika Anda menggunakan tugas ekspor, hal yang Anda ekspor sudah bisa memiliki implements di tempat lain.

Disetujui. Kami lebih memilih sintaks

export implements Showable;

dan setuju bahwa ini tidak diperlukan untuk file export = assignments.

Beberapa pertanyaan lagi:

  • Karena kita mengizinkan modul memiliki tipe di situs deklarasi, sebaiknya kita tidak mengizinkan modul memiliki tipe di situs penggunaan juga. misalnya:
declare module "Module" implements Interface { }

import i : Interface = require("Module");
  • Apa yang Anda lakukan dengan deklarasi gabungan, haruskah Anda menerapkan antarmuka pada agregat dari semua deklarasi? dan apa yang terjadi jika mereka tidak cocok dalam visibilitas?
    misalnya:
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;

Ini harus diizinkan pada modul eksternal ambien. Untuk modul ini, dua sintaksis harus diizinkan menurut saya:

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

Atau haruskah Bar berada dalam ruang lingkup dalam klausa implement sebelum pembukaan { ?

Menambahkan info jenis ke pernyataan impor tidak terlalu berguna menurut saya, karena Anda dapat menambahkan info jenis ke modul itu sendiri.

Dan untuk deklarasi gabungan, saya akan mengatakan bahwa blok modul yang berisi klausa implement harus mengimplementasikan antarmuka. Itu juga mencegah masalah dengan visibilitas.

Bagaimana ini akan terkait dengan # 2159? Namespace mengimplementasikan antarmuka?

@jbondc Jika kita punya ini, itu juga akan berlaku untuk ruang nama. Anda harus memikirkan modul internal dan namespace sebagai isomorfik.

Anda yakin ingin menggunakan jalur implementasi di mana "ruang nama" dapat mengimplementasikan antarmuka?

Oh wow, ini sudah disetujui cukup lama. @RyanCavanaugh , @DanielRosenwasser , @mhegazy kecuali jika Anda memiliki pemikiran kedua atau penyesuaian, saya mungkin akan segera menerapkan ini.

Saya menarik skeptisisme saya sebelumnya, saya benar-benar keluar untuk kemungkinan struktural baru yang akan dibawanya.

Sejalan dengan itu, mohon pertimbangkan untuk menerapkan antarmuka agregat antarmuka daripada hanya blok yang mendeklarasikan implementasi - Sifat ruang nama / modul harus disebarkan dan berisi banyak komponen non-sepele. Saya ingin bisa menggunakan ini, tapi saya pasti tidak ingin mendefinisikan seluruh namespace / modul saya dalam file yang sama. Mengapa tidak menggunakan kelas saja dalam kasus itu?

@ Elephant-Vessel Saya tidak yakin apakah kita berbicara tentang Modul, atau Ruang nama, atau Paket, atau Fitur, atau ...

@aluanhaddad Apa maksudmu?

Maksud saya pada saat modul diskusi ini dimulai tidak berarti apa artinya hari ini. Kami sekarang menggunakan istilah namespace untuk merujuk pada apa yang dijelaskan dalam OP sebagai modul, sementara modul telah mengambil arti yang lebih tepat dan tidak kompatibel. Jadi ketika Anda berbicara tentang banyak file yang mengambil bagian dalam implementasi ini, apakah Anda mengacu pada namespace atau modul?

Saya mengacu pada ruang nama. Saya kira saya hanya ingin menyesuaikan diri dengan riwayat utas ini, maaf karena tidak lepas :) Atau ketika saya memikirkannya, mungkin saya memiliki istilah umum 'modul' di kepala saya, menggambarkan unit tingkat yang lebih tinggi yang terdiri dari set sub-komponen, dirakit untuk menyediakan fungsionalitas tingkat tinggi tertentu dalam suatu sistem. Tapi saya baik-baik saja dengan hanya menggunakan 'namespace'.

Jadi saya ingin dapat mendeskripsikan dan meletakkan batasan dan harapan pada [modul _generic_] yang dapat berisi [modul _generik_] atau kelas lain, memanfaatkan ruang nama konsep struktural dalam skrip ketikan.

Harapan saya adalah bahwa kita akan dapat mengekspresikan ekspektasi struktural tingkat yang lebih tinggi dengan lebih baik dalam suatu sistem. Kelas tidak berskala dengan baik, mereka baik-baik saja sebagai komponen atom dalam suatu sistem, tetapi saya tidak berpikir bahwa struktur organisasi tingkat yang lebih tinggi dalam sistem akan baik untuk diekspresikan dengan kelas karena mereka dirancang untuk dipakai dan diwariskan dan hal-hal seperti itu . Itu terlalu membengkak.

Saya menghargai cara yang sederhana dan bersih untuk menggambarkan struktur tatanan sistem yang lebih tinggi, tanpa masalah. Lebih disukai dengan satu-satunya keributan menjadi kendala visibilitas arah opsional. Seperti membuat tidak mungkin untuk mereferensikan _MySystem.ClientApplication_ dari _MySystem.Infrastructure_ tetapi baik-baik saja sebaliknya. Kemudian kami akan mulai pergi ke tempat yang menyenangkan.

@ Elephant-Vessel terima kasih telah mengklarifikasi. Saya setuju ini akan sangat berharga dan jenis kelas bukanlah pendekatan yang tepat di sini. Saya pikir Anda tepat sasaran ketika berbicara tentang instantiation karena namespace mewakili hal-hal yang secara konseptual adalah lajang di tingkat perpustakaan. Meskipun ini tidak dapat diterapkan, akan berguna secara konseptual untuk memiliki sesuatu yang tidak _imply_ beberapa contoh.

Saya setuju dengan @ Elephant-Vessel. Meskipun mudah untuk salah mengira TypeScript sebagai Java lain, di mana semua batasan diekspresikan dengan struktur kelas tunggal, TS memiliki konsep "Bentuk" yang jauh lebih luas yang sangat kuat dan menghilangkan kontortonisme semantik. Sayangnya, ketidakmampuan untuk menempatkan batasan pada modul cenderung memaksa pengembang untuk kembali ke pola kelas untuk hal-hal yang akan lebih baik dinyatakan sebagai modul.

Misalnya, untuk pengujian unit, akan sangat membantu jika dapat mengekspresikan beberapa "bentuk" (yaitu batasan) pada modul sehingga kami dapat memberikan implementasi alternatif untuk konteks berjalan tertentu. Sekarang, tampaknya satu-satunya cara untuk melakukan itu dalam struktur / cara diperiksa adalah kembali ke kelas berbasis DI (sebagai la Spring) dan membuat semuanya menjadi kelas (dan karena itu instantiable).

Bagaimanapun, saya memparafrasekan @ Elephant-Vessel, tetapi jika saya memiliki satu keinginan untuk TS, itu akan menjadi yang ini.

Ada kabar tentang burung ini? Saya memiliki masalah ini juga

soooo, uhh, bukankah itu kasus sederhana:

export {} as IFooBar;

apa yang salah dengan sintaks itu? Saya kira sintaksnya telah disetujui, mungkin sebagai

export implements IFooBar

tetap menantikannya

Apakah ini sudah diterima / mendarat? ini akan menjadi fitur yang keren

Bagaimana kita bisa mengembangkannya? Ini sangat kuat. Senang bisa membantu!

ada pekerjaan di birb ini? Satu pertanyaan yang saya miliki saat ini, adalah bagaimana saya dapat mendeklarasikan antarmuka untuk ekspor default. Sebagai contoh:

export default {}

Saya kira saya bisa melakukan:

const x: MyInterface = {}
export default x;

yang akan bekerja untuk sebagian besar file TS, masalahnya adalah jika Anda membuat kode untuk JS terlebih dahulu dan berencana untuk beralih ke TS nanti, maka ini tidak berfungsi dengan baik.

Hal lain yang saya pikirkan, bagaimana dengan namespace yang diimplementasikan? Sesuatu seperti:

export namespace Foo implements Bar {

}

Saya kira Bar akan menjadi namespace _abstract_ lol idk

Melihat pertanyaan ini muncul berkali-kali, dan saya pikir kita semua hanya mencari satu hal:
Mendukung anggota statis dalam sebuah antarmuka.
Jika itu akan terjadi, Anda bisa menggunakan kelas dengan anggota statis dan antarmuka, yang hampir sama dengan yang Anda coba lakukan di sini, bukan?

Either way, menambahkan dukungan statis ke antarmuka ATAU menambahkan dukungan antarmuka untuk modul sangat dibutuhkan.

@shiapetel nah

kita bisa melakukan ini:

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

tapi bukan itu yang kami cari. kami secara khusus mencari:

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

tetapi saat ini tidak ada mekanisme untuk menerapkan modul untuk mengekspor foo dan bar. Dan pada kenyataannya tidak ada mekanisme untuk memaksakan bahwa modul mengekspor nilai default yang benar.

Jika antarmuka mendukung anggota statis, Anda dapat menggunakan kelas dengan foo / bar statis yang diwarisi dari:
Antarmuka ILoveFooBar {
foo statis
bilah statis
}

Baik?
Itulah yang saya maksud, saya pikir itu akan membantu dalam situasi Anda- Saya tahu itu pasti akan membantu dalam situasi saya.

Anggota statis

Apakah masalah ini hanya menunggu seseorang untuk mencoba menerapkannya?

Salah satu kasus penggunaan adalah untuk kerangka kerja dan alat yang memindai direktori untuk modul saat memulai aplikasi, mengharapkan semua modul tersebut mengekspor bentuk tertentu.

Misalnya, Next.js memindai ./pages/**/*.{ts,tsx} untuk modul halaman Anda, menghasilkan rute berdasarkan nama file Anda. Terserah Anda untuk memastikan setiap modul mengekspor hal yang benar ( NextPage sebagai ekspor default, dan opsional PageConfig export bernama 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 }
}

Alangkah baiknya jika Anda dapat mendeklarasikan bentuk ekspor seluruh modul dalam satu baris di dekat bagian atas, seperti implements NextPageModule<Props> .

Pemikiran lain: akan menarik jika ada beberapa cara untuk menentukan dalam konfigurasi TypeScript bahwa semua file yang cocok dengan pola tertentu (seperti ./pages/**/*.{ts,tsx} ) harus menerapkan bentuk ekspor tertentu, sehingga modul dapat memiliki jenis ekspornya- diperiksa murni karena terletak di dalam direktori pages misalnya. Tetapi saya tidak yakin apakah ada preseden untuk pendekatan ini, dan itu mungkin membingungkan.

Saya sering tergoda untuk membuat Singleton Class ketika saya hanya membutuhkan modul sederhana yang mengimplementasikan antarmuka. Ada tip bagaimana cara terbaik untuk mengatasi ini?

@RyanCavanaugh @DanielRosenwasser
Saya ingin menangani masalah ini. Bisakah Anda memberi saya beberapa tip untuk solusinya atau di mana harus melihat-lihat?

Memikirkan hal ini dari perspektif tahun 2020, saya bertanya-tanya apakah alih-alih export implements Showable kita menggunakan kembali type dan mengizinkan export sebagai pengenal? Hari ini sintaksis itu

Kemudian kita mendapatkan sintaks impor:

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

Deklarasi kemudian mudah ditulis:

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

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

Perlu juga dipikirkan apa yang setara dengan JSDoc, mungkin:

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

Ada beberapa catatan di ^ - satu hal menarik yang keluar dari pertemuan tersebut adalah gagasan bahwa kita dapat membangun alat yang lebih umum di mana ini adalah kasus penggunaan, daripada satu-satunya hal yang dilakukannya.

Misalnya, jika kita memiliki operator pernyataan tipe untuk kompatibilitas tipe maka itu bisa digunakan untuk ekspor modul, dan secara umum untuk memverifikasi bahwa tipe cocok dengan yang Anda inginkan. Sebagai contoh:

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

Dimana kurangnya target berarti menerapkannya pada cakupan level teratas. Ini dapat digunakan untuk menyediakan pengetikan kontekstual (mis. Anda akan mendapatkan pelengkapan otomatis di export default { en|

Tetapi juga bisa berguna dalam memvalidasi tipe Anda sendiri:

import {someFunction} from "./example"

type assert ReturnType<typeof someFunction> is string

Perlu juga dipikirkan apa yang setara dengan JSDoc, mungkin:
js /** <strong i="7">@typedef</strong> {import ("webpack").Config} export */

Saya akan berpikir @module akan setara dengan JSDoc. Bagian atas file harus memiliki:
js /** <strong i="12">@module</strong> {import("webpack").Config} moduleName */

Lihat: https://jsdoc.app/tags-module.html

Buku Cerita v6 telah berubah menjadi pendekatan berdasarkan modul terstruktur yang mereka sebut Format Cerita Komponen . Semua modul .stories.js/ts dalam basis kode diharapkan menyertakan ekspor default dengan tipe Meta .

Tidak adanya cara untuk mengekspresikan harapan ini secara global, dikombinasikan dengan kekurangan yang ada dalam pengetikan ekspor default, membuat penggunaan Storybook v6 dengan TypeScript menjadi pengalaman yang jauh lebih mulus daripada yang seharusnya.

Untuk menambah poin @jonrimmer , mengekspor default yang merupakan type yang mereplikasi module akan menyebabkan masalah dengan pengocokan pohon.

Webpack tidak memiliki masalah pohon gemetar import * as Foo . Tetapi ketika Anda mencoba melakukan hal yang sama dengan export default const = {} atau export default class ModuleName { dengan semua anggota statis, impor yang tidak digunakan tidak dihapus.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat