Typescript: اسمح للوحدة بتنفيذ واجهة

تم إنشاؤها على ١٠ أغسطس ٢٠١٤  ·  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 كوحدة نمطية ، بينما اتخذت الوحدة النمطية معنى أكثر دقة وغير متوافق. لذلك عندما تتحدث عن ملفات متعددة تشارك في هذا التنفيذ ، هل تشير إلى مساحات الأسماء أو الوحدات النمطية؟

أنا أشير إلى مساحات الأسماء. أعتقد أنني أردت فقط التوافق مع تاريخ هذا الموضوع ، آسف لعدم كسره :) أو عندما أفكر في الأمر ، ربما كان لدي المصطلح العام "وحدة" في رأسي ، يصف وحدة ذات مستوى أعلى تتكون من مجموعة من المكونات الفرعية ، مجمعة لتوفير وظائف معينة عالية المستوى في النظام. لكني بخير مع مجرد استخدام "مساحات الأسماء".

لذلك أريد أن أكون قادرًا على وصف ووضع القيود والتوقعات على [_الوحدات النمطية العامة_] التي يمكن أن تحتوي على [_ الوحدات النمطية العامة_] أو الفئات الأخرى ، مع الاستفادة من مساحات أسماء المفاهيم الهيكلية في الكتابة المطبوعة.

آمل أن نتمكن من التعبير بشكل أفضل عن التوقعات الهيكلية عالية المستوى في النظام. لا تتسع الفصول بشكل جيد ، فهي جيدة كمكونات ذرية في نظام ما ، لكنني لا أعتقد أن الهيكل التنظيمي عالي المستوى في نظام ما سيكون جيدًا للتعبير عنه بالفئات لأنها مصممة ليتم إنشاء مثيل لها وتوريثها وأشياء من هذا القبيل . إنه مجرد منتفخ للغاية.

سأقدر طريقة بسيطة وواضحة لوصف هيكل النظام الأعلى ، دون ضجة. ويفضل أن تكون الضجة الوحيدة هي قيود الرؤية الاتجاهية الاختيارية. مثل جعل من المستحيل الرجوع إلى _MySystem.ClientApplication_ من _MySystem.Infrastructure_ ولكن بطريقة جيدة بالعكس. ثم نبدأ بالذهاب إلى مكان مثير.

@ Elephant-Vessel شكرا للتوضيح. أوافق على أن هذا سيكون ذا قيمة كبيرة وأن أنواع الفصول الدراسية ليست هي النهج الصحيح هنا. أعتقد أنك ضربت المسمار على رأسك عندما تتحدث عن إنشاء مثيل لأن مساحات الأسماء تمثل أشياء مفردة من الناحية المفاهيمية على مستوى المكتبة. على الرغم من أنه لا يمكن فرض ذلك ، سيكون من المفيد من الناحية المفاهيمية أن يكون لديك شيء لا _imply_ عمليات إنشاء متعددة.

أتفق مع @ Elephant-Vessel. في حين أنه من السهل الخلط بين TypeScript وجافا أخرى ، حيث يتم التعبير عن جميع القيود ببنية فئة واحدة ، فإن TS لديها مفهوم "شكل" أوسع بكثير وهو قوي للغاية ويزيل الالتواء الدلالي. لسوء الحظ ، فإن عدم القدرة على وضع قيود على الوحدة النمطية تميل إلى إجبار المطورين على الرجوع مرة أخرى إلى نمط الفصل للأشياء التي من الأفضل التعبير عنها كوحدة نمطية.

على سبيل المثال ، بالنسبة لاختبار الوحدة ، سيكون من المفيد جدًا أن تكون قادرًا على التعبير عن بعض "الشكل" (أي القيود) على الوحدات النمطية حتى نتمكن من توفير تطبيق بديل لسياق تشغيل معين. الآن ، يبدو أن الطريقة الوحيدة للقيام بذلك بطريقة هيكلية / تم فحصها هي العودة إلى 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 {

}

أعتقد أن شريط سيكون _abstract_ مساحة اسم لول idk

رأيت هذا السؤال يطرح عدة مرات ، وأعتقد أننا جميعًا نبحث فقط عن شيء واحد:
دعم الأعضاء الثابتة في واجهة.
إذا حدث ذلك ، يمكنك فقط استخدام فصل دراسي به أعضاء ثابتون وواجهة ، وهو نفس الشيء تقريبًا الذي تحاول القيام به هنا ، أليس كذلك؟

في كلتا الحالتين ، أضف دعمًا ثابتًا للواجهات أو أضف دعمًا للواجهة للوحدات النمطية.

shiapetel nah ليس من هذا القبيل.

نستطيع فعل ذلك:

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

لكن هذا ليس ما نبحث عنه. نحن نبحث على وجه التحديد عن:

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

ولكن لا توجد حاليًا آلية لفرض الوحدة لتصدير foo و bar. وفي الحقيقة لا توجد آلية لفرض تصدير الوحدة للقيمة الافتراضية الصحيحة أيضًا.

إذا كانت الواجهات تدعم الأعضاء الثابتة ، فيمكنك استخدام فئة بها foo / bar ثابت موروث من:
واجهة ILoveFooBar {
ثابت foo: FooType ؛
شريط ثابت
}

حق؟
هذا ما قصدته ، أعتقد أنه سيساعد في وضعك - أعلم أنه سيساعدني بالتأكيد.

قد تكون أعضاء واجهات 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 على سبيل المثال. لكنني لست متأكدًا مما إذا كانت هناك أي سابقة لهذا النهج ، وقد يكون الأمر محيرًا.

أجد أنني غالبًا ما أميل إلى إنشاء فصل فردي عندما يكون كل ما أحتاجه هو وحدة نمطية بسيطة تنفذ واجهة. أي نصائح كيف أفضل لمعالجة هذا؟

RyanCavanaughDanielRosenwasser
اريد العمل على هذه القضية. هل يمكنك أن تعطيني بعض النصائح عن الحل أو أين أنظر حولي؟

بالتفكير في هذا من منظور 2020 ، أتساءل عما إذا كنا نعيد استخدام type بدلاً من export implements Showable ونسمح لـ 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 إلى نهج يعتمد على وحدات منظمة أطلقوا عليها اسم تنسيق قصة المكون . من المتوقع أن تتضمن كافة الوحدات النمطية .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 التقييمات