Typescript: مفتاح التعزيز أثناء تعيين النوع

تم إنشاؤها على ٨ ديسمبر ٢٠١٦  ·  88تعليقات  ·  مصدر: microsoft/TypeScript

تتبع Aurelia الاصطلاح القائل بأنه لأي حقل foo ، عندما يتم تغيير foo بواسطة إطار العمل ، يتم إجراء محاولة لاستدعاء fooChanged() . لا توجد (على ما يبدو) طريقة لوصف هذا الاصطلاح كنوع (تلقائيًا) عبر وظيفة Mapped Type وحدها.

أود فتح نقاش حول احتمال زيادة مفاتيح الملكية أثناء رسم الخرائط.

على سبيل المثال: عن طريق مفصل:

type Changed<T> = 
{          
   [ P in keyof T ] + "Changed" ?: Function;
}

class Foo{
  field:number;
}

let foo = <Foo & Changed<Foo>> new Foo();

foo.field = 10;

if(foo.fieldChanged)
  foo.fieldChanged();

في حالة الاستخدام هذه على وجه التحديد ، من المحتمل أن تتطلب https://github.com/Microsoft/TypeScript/issues/12424 أيضًا ، ولكن هذا بجانب النقطة هنا.

Literal Types Mapped Types In Discussion Suggestion

التعليق الأكثر فائدة

تم تنفيذ هذا الاقتراح الآن في # 40336.

ال 88 كومينتر

مثال آخر ذو صلة هو برنامج بلوبيرد وعد الكل . على سبيل المثال:

type Before = {
    foo: (callback: (err, res) => void) => void
    bar: (x: string, callback: (err, res) => void) => void
}

// promisifyAll would map Before to After - note all keys have 'Async' appended:
type After = {
    fooAsync: () => Promise<any>;
    barAsync: (x: string) => Promise<any>;
}

let before: Before;
let after: After = promisifyAll(before);



أعتقد أن العمليات المعقدة غير العادية مثل هذه يجب أن تدعمها أداة إنشاء البرامج مثل # 9883.

استخرج هذا الأمر لأنني أتصارع مع حالة استخدام لهذا الآن: تحديد نطاق إجراءات الاستعادة.
لدي واجهة Action :

interface Action<Type extends string> {
  type: Type
}

لتجنب التعارض مع (على سبيل المثال) مكتبات الطرف الثالث ، أقوم بتحديد نطاق أفعالي ، على سبيل المثال مثل هذا:

export const createScopedActionType = _.curry((scope: string, type: string) => `${scope}/${type}`);

const createActionInMyScope = createScopedActionType("MyScope");
const MY_ACTION = createActionInMyScope("MY_ACTION");

من المعروف في وقت الترجمة أن MY_ACTION سيكون له قيمة "MyScope/MY_ACTION" ويجب أن يكون هذا هو نوعه. المطبوع على أية حال يراه كسلسلة.

من الواضح أنه يمكنني تعيين القيمة الناتجة مباشرة ، ولكن هذا من شأنه أن يقلل من قابلية الصيانة لأنه باستخدام النهج الموضح أعلاه ، يمكنني ببساطة إعادة استخدام وظيفتي المطبقة جزئيًا. لذلك لا بد لي من تحديد النطاق الخاص بي مرة واحدة فقط. إذا كنت سأستخدم التعيينات العادية ، فسيتعين علي تغيير أسطر متعددة من التعليمات البرمجية (وأواجه خطر فقدان مثيل!) إذا تغير اسم النطاق.

يجب أن ينتج عن تقييم وقت التجميع للوظائف التي يتم استدعاؤها بالحروف (إذا كانت قابلة للتقييم ، كما في: لا توجد آثار جانبية ، لا يوجد تعديل للمعامل => وظائف خالصة) النوع / القيمة النهائية التي يتم إرجاعها بواسطة استدعاء الوظيفة.

لدى C ++ مفهوم مشابه مع تعبيرات ثابتة ( constexpr ).

لست متأكدًا من المدى الذي ستسمح به Typescript بتنفيذ مثل هذه الميزة ، ولكنها ستكون مساعدة كبيرة في الحفاظ على الكود قابلاً للصيانة والوحدات النمطية.

قد يكون من الجيد إعادة النظر في هذه المشكلة في ضوء التحسينات 2.8. على وجه التحديد ، أعتقد أن هذه المشكلة هي الشيء الوحيد الذي يمنع التنفيذ الجيد لـ Bluebird.promisifyAll . تم ذكره سابقًا في هذا الموضوع ، لكنني أعتقد أن الاستدلال بنوع الإرجاع كان جزءًا مهمًا من اللغز الذي كان مفقودًا أيضًا ، حتى الآن.

في مكتبتنا ، تحتوي فئة Component لكل من خصائصها الخاصة (هنا id ) على وظيفة جامع عامة مرتبطة:

class Component { 
    private _id: string;
    public get_id() { 
        return this._id;
    }
}

لكل مكون لدينا واجهة إضافية Properties والتي - في هذه الحالة - لها خاصية id :

interface Properties {
    id:? string;
}

هناك الكثير من التكرار المعرض للخطأ هنا والذي سبب لنا الكثير من المتاعب في الماضي. مع ميزة الأنواع الشرطية الجديدة في TypeScript 2.8 ، أود أن أرى بنية بنمط linq في الأنواع المفهرسة:

type Properties<T extends Component> =
{
    [ P in keyof T where P.startsWith( 'get_' ) select P.replace( 'get_', '' ) as K ]: ReturnType<T[K]>;
}                                                                                            |
                                                                                             ReturnType is defined in TypeScript 2.8  
let componentProperties: Properties<Component>;
componentProperties.id = "foo"; // string is fine
componentProperties.id = true; // error

في Loopback ، يتم تحديد شرط طلب DB على النحو التالي:

{ order: 'field ASC' }

أو

{ order: [ 'field1 DESC', 'field2 ASC' ] }

أعتقد أن هذه الميزة مطلوبة لـ TS لكتابة هذا الحقل بشكل مناسب.

وهذا أمر مهم جدا لأشياء مثل استخدام Bluebird.js إلى promisify كل شيء في وحدة نمطية، كماyortus وذكرRetsam سابقا على ذلك.

باستخدام keyof ، أصبح من الممكن الآن تحويل أنواع الوحدات النمطية مثل import fs from 'fs' الذي يتم تمريره إلى Promise.promisifyAll(...) . مما يعني أنه يمكن كتابتها تلقائيًا تقريبًا . (وأنا أختلف بشدة معsaschanaz مدونة جيل ليست الأداة المناسبة لهذه المهمة. يمكننا أن نفعل ذلك تلقائيا!)

الشيء الوحيد المفقود لكي يعمل هذا هو الميزة التي طلبتها هذه المشكلة: دعم تعبير [expression] + [string literal] في الأنواع. لا أعتقد أنه سيكون صعبًا للغاية ، وسأكون سعيدًا للبدء في ذلك وتقديم طلب سحب ، إذا كان بإمكان شخص ما توجيهي في الاتجاه الصحيح من أين أبدأ!

@ sb-js يبدو أن هذا سيكون مكانًا جيدًا للبدء ، حيث ستحتاج إلى إضافة القليل من شأنه تحليل علامة الجمع بعد معلمة النوع المعين.

drsirmrpresidentfathercharles شكرا للمؤشر. سأعطيها فرصة هذا الأسبوع.

قضية أخرى:

interface IActionsA {
    update: number;
    clear: number;
}

interface IActionsB {
    update: number;
    clear: number;
}

interface INamespaceMap {
    'moduleA/': IActionsA;
    'moduleA/moduleB/': IActionsB;
}

// No matter how to write it
type ConnectKey<N, T> = N + [ P in keyof T ];


interface Dispatch<Map> {
    <N extends keyof Map, K extends keyof Map[N]>(type: ConnectKey<N, K>, value: Map[N][K]): void;
}

let dispatch!: Dispatch<INamespaceMap>;
dispatch('moduleA/update', 0);
dispatch('moduleA/moduleB/delete', 0);

هذا سوف يجلب مساعدة كبيرة لل vuex .

أنا أكتب مكتبة تحول بعض القيم إلى أشياء يمكن ملاحظتها:

inteface Foo {
  bar: any
}

// the library should convert above interface to 

inteface Foo2 {
  bar$: any
}

// syntax like below would be cool
type Foo2 = {
    [(K in keyof Foo) + '$']: Observable<Foo[K]>
}

// such expressions would be useful not only to mutate property names, 
// but other strings in type system as well (this behavior is not desired as much, though):
type Foo = 'bar'
type Foo2 = Foo + 'Addition' // gives the string type "barAddition"

قضية أخرى.
في أنماط ورقة الأنماط <strong i="7">@media</strong> ... string. على سبيل المثال:

{
   '<strong i="10">@media</strong> ios': { ... },
   '<strong i="11">@media</strong> (min-width: 350) and (max-width: 500)': { ... }
}

أحتاج حاليًا إلى تعداد جميع المفاتيح المستخدمة في التطبيق يدويًا:

type ExtraStyles = {
    '<strong i="15">@media</strong> ios'?: ExtendedStyle;
    '<strong i="16">@media</strong> (min-width: 350) and (max-width: 500)'?: ExtendedStyle;
    ...
}

سيكون من الرائع تحديد النوع العام بمفاتيح تطابق التعبير العادي:

type MediaQueryStyles = {
    [/<strong i="20">@media</strong> .+/i]?: ExtendedStyle;
}

هذا من شأنه أن يساعد كثيرًا في دعم مفاتيح بناء الجملة على غرار lodash.get ، على ما أعتقد.

وجود أنواع Split<T extends string> و Join<T extends string[]> سيساعد أيضًا.

سيكون دعم مفاتيح بناء الجملة أمرًا رائعًا. على سبيل المثال ، تتم كتابة استعلامات MongoDB بشكل متكرر كـ {"obj.nested.key": "value"} وهو أمر يستحيل كتابته الآن.

حالة أخرى:

كنت أتساءل ما الذي يمكن عمله فيما يتعلق بـ pnp / pnpjs # 199 في pnpjs . باستخدام pnpjs ، يمكنك الاستعلام عن مجموعات من العناصر في SharePoint وعرض مجموعة فرعية فقط من خصائص تلك العناصر التي ستحتاجها باستخدام الوظيفة select . على سبيل المثال:

documents.select('Id', 'Title', 'CreatedBy').get();

تدعم المكتبة أيضًا تحديد الخصائص الموسعة للأنواع المعقدة. في المثال أعلاه ، سنتمكن من الحصول على الخصائص الفرعية Id و UserName للخاصية CreatedBy :

documents.select('Id', 'Title', 'CreatedBy/Id', 'CreatedBy/UserName').get();

من المستحيل حاليًا كتابة الوسيطات select أو نوع الإرجاع الخاص بها. تقوم المكتبة حاليًا بإلقاء كل شيء على any افتراضيًا وهو أمر مخزٍ لأن جميع سيناريوهات الاستخدام لهذه المكتبة تقريبًا تستخدم هذه العمليات.

أي مزيد من المعلومات حول ما إذا كان هذا سيكون احتمالًا؟ استعلامات الترقيم النقطي هي واحدة من آخر الأشياء المفقودة في الكتابة في مشروعي =)

حسنًا ، ليس به أي اختبارات أو أي شيء ، لكن لدي تطبيق عملي في

ahejlsberg أي أفكار حول هذا؟

يجب ألا يخترع المطبوع كلمات رئيسية جديدة ويكتب رموز المستوى بين الحين والآخر.
يمكن للطباعة المطبعية تقديم أنواع عامة مضمنة لمثل هذه الأشياء على سبيل المثال

type StringConcat<A, B> = /* built-in, resolves to `${A}${B}` when used */
type NumberAdd<A, B> = /* built-in, resolves to A+B when used */
const s: StringConcat<'foo', 'bar'> = 'foobar' // pass
const s: StringConcat<'foo', 'bar'> = 'foo' + 'bar' // pass
const s: StringConcat<'foo', 'bar'> = 'foo' + 'quu' // fail

وبالمثل ، يمكن أن تكون عوامل تشغيل الأرقام مفيدة:

const n: NumberAdd<1, 2> = 3 // pass
const n: NumberAdd<1, 2> = 1 + 1 + 1 // pass
const n: NumberAdd<1, 2> = 1 + 1 + 2 // fail

anilanar لجعل هذا العمل لوضع رسم الخرائط ، أفترض

StringConcat<'foo' | 'bar', 'quu'>

ستقرر ل

'fooquu' | 'barquu'

؟

anilanar أحفر هذه الفكرة ؛ كيف يبدو في مثال تعيين الكائن ، مثل ما يظهر في هذا التعليق ؟

@ charles-toller def'ly. StringConcat توزع أكثر من | و & .

mAAdhaTTah [K in StringConcat<keyof MyInterface, '$'>]

أعتقد أنني أفضل بناء جملة يتيح لنا الاستفادة من العمليات الحالية ، بدلاً من إعادة تعريفها بشكل فردي. على سبيل المثال ، StringConcat<T extends string, U extends string> لا يتعامل مع استبدال regex - إذا كنت تريد أن تحتاج إلى StringReplace<T extends string, PATTERN extends string, REPLACEMENT extends string> . وبالمثل ، بالنسبة للعدد ، فإن الحاجة إلى إعادة تعريف الجمع والطرح والضرب وما إلى ذلك لأنواع عامة ستكون غير جذابة من الناحية الجمالية ، على الأقل. نظرًا لأن const x: 'a' + 'b' = 'ab'; يعد حاليًا خطأ في بناء الجملة ، فإن بناء الجملة هذا يبدو أكثر إيجازًا واتساقًا بالنسبة لي.

فيما يتعلق بالنحو ، لا أعتقد أن الأناقة الجمالية يجب أن تكون الشغل الشاغل. يعد إدخال المشغلين الذين يصعب التخلي عنهم لاحقًا أمرًا محفوفًا بالمخاطر. الأنواع العامة هي بالفعل جزء من اللغة.

NumberAdd على بعض الخصائص الرائعة إذا كان TS يحتوي على أنواع متكررة ، يمكنك تحديد جميع عوامل تشغيل الأرقام فقط باستخدام NumberCompare و NumberAdd باستخدام الأنواع الشرطية والعودية.

أعتقد أنه يجب على TS تقديم عناصر متعامدة مدمجة دون ملء TS بالمشغلين / الكلمات الرئيسية التي تبدو وكأنها متسللين بدلاً من كونها جزءًا من لغة جيدة التنظيم.

مع اقتراحك ، + هو الاسم المستعار للمشغل الثنائي لـ StringConcat . إذا كان المجتمع يريد / يحتاج إلى أسماء مستعارة للمشغل على مستوى النوع أو السكريات النحوية للأنواع العامة المضمنة (أو التحميل الزائد للمشغل ، ولكن للأنواع) ، فسيكون هذا اقتراحًا آخر.

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

حالة استخدام أخرى لهذه الميزة: https://github.com/drcmda/react-three-fiber/issues/11

react-three-fiber هي مكتبة توفر كل فئة تصدرها THREE.js كعناصر React JSX مع الحرف الأول من الأحرف الصغيرة (بحيث يتم التعامل معها كعناصر "host" بواسطة React وإعادة توجيهها إلى المصلح).

لا يحتفظ react-three-fiber بقائمة بما يتم تصديره ، بل يحل الصادرات في وقت التشغيل .

في حالة وجود ميزة تحويل سلسلة تشبه constexpr ، يمكن إنشاء الأنواع الخاصة بتوسيع واجهة JSX.IntrinsicElements تلقائيًا استنادًا إلى صادرات THREE و keyof .

تركز التعليقات أعلاه على هذا الاقتراح على تسلسل السلسلة ، لكنها لن تكون كافية لحالة استخدام react-three-fiber .

تضمين التغريدة

رد فعل - ثلاثة - الألياف لا يحتفظ بقائمة من ما يتم تصديره ، فإنه يحل الصادرات في وقت التشغيل.

ربما يمكنك إنشاء تعريفات الأنواع تلقائيًا في المرحلة postinstall مع الرجوع إلى الأنواع التي تم إنشاؤها باستخدام الإصدار الأخير من three.js أثناء النشر؟

لا أعتقد أن TS سيكون لديها ميزة في أي وقت قريب من شأنها أن تحل مشكلتك.

أحاول تحقيق ما ينفذهanurbol بالضبط. اقتراحه رائع وسهل القراءة. يمكن استخدام الاستيفاء المتسلسل أيضًا:

type Observablify<T> = {
   [`${P in keyof T}$`]: Observable<T[P]>;
}

شخصيا ، بناء جملة مشابه لفهم قائمة بايثون يبدو أكثر "صحيحا" ، وأنا أحب التفكير في بناء الجملة:

type Observablify<T> = {
   [(P + '$') for P in keyof T]: Observable<T[P]>;
}

لقد انتظرت هذا أيضًا! ستكون ميزة قاتلة لكني فقدت الأمل ...

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

وبدون ذلك ، من الصعب كتابة نقطة الإعادة. ليس إلا إذا كنت على ما يرام مع كتابة أطنان من النماذج غير الضرورية ، ببساطة لأنك لا تستطيع تعميم مجموعة واسعة من الأنماط.

grapereader إنه "خطأ" إعادة الإنتاج بمعنى نقل الكثير من المعلومات في سلاسل. استخدم النقابات ذات العلامات للتعامل مع أشياء مثل الجلب والنجاح وفشل الجلب.

type StartAction<A> = { type: 'fetch', phase: 'start'  payload: A };
type SuccessAction<B>= { ... };
type FetchAction = StartAction | SuccessAction;

const handleFetch = <A, B>(action: FetchAction, handlers: { start: (action: StartAction<A>) => void, ... }): void;

تحصل على الجوهر.

هل سنقوم حقًا بتصميم TS استنادًا إلى عيوب تصميم التصحيح الخاصة برمز أرض المستخدم في JS؟

لا يعني ذلك أنني ضد وظائف مستوى النوع للتعامل مع القيم الحرفية للسلسلة. أعتقد فقط أن TS يمكنه التركيز على بعض المجالات الأخرى في الوقت الحالي.

بينما تعجبني حقًا فكرة النوع العام الذي يزيد السلاسل ، لأنه يبدو أقرب كثيرًا إلى كيفية زيادة الكائنات أيضًا بالتعيين ، فإن StringConcat يواجه عقبة في طريق التنفيذ: إنه مرجع نوع لا يمكن يتم وضعها في أي lib.d.ts ، مما يجعلها غير قابلة للحل بدون حالة خاصة في المدقق. والسبب هو أنه على عكس العديد من برامج TS المضمنة الأخرى مثل ThisType أو Omit ، لا يمكن تمثيلها مباشرة في TypeScript. حتى الأنواع التي لها معنى فقط للمدقق ، مثل ThisType ، ليس لديها مشكلة في وضعها في libs لأنها لا تغير في الواقع نوع الكائن الذي تقوم بتعديله. من ناحية أخرى ، يقوم StringConcat بتعديل وسيطات النوع الخاص به مباشرةً ، وبالتالي فإن أقرب قيمة صحيحة يمكننا تحديدها كنوع هي string ، وهو أمر غير مرغوب فيه بشكل واضح.

هل سنقوم حقًا بتصميم TS استنادًا إلى عيوب تصميم التصحيح الخاصة برمز أرض المستخدم في JS؟

نعم فعلا. الهدف الأول لـ TypeScript هو "التحديد الثابت للتركيبات التي من المحتمل أن تكون أخطاء." هذه المشكلة مليئة بمشرفي المكتبة الذين يحتاجون إلى هذه الميزة من أجل تحقيق هذا الهدف لمستخدميهم النهائيين.

يمكن تحسين الكتابة لواجهة المستخدم المادية ، على سبيل المثال ألوان لوح الألوان
مثال:

<Box color="primary.main">…

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

في الأساس شيء مثل:
export type CamelCased<T> = {[camelCase(K in keyof T)]: T[K]};

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

أنا أيضا أود أن أرى هذه الميزة. في قاعدة الشفرة الخاصة بنا ، لدينا مكونات ذات ترتيب أعلى قابلة للتكوين تسمح بتخصيص الخصائص الافتراضية على كائن ملفوف. في هذه الحالة ، عند تكوين اسم خاصية (على سبيل المثال ، prop ) ، نقوم أيضًا تلقائيًا بتكوين إصدار "افتراضي" (على سبيل المثال defaultProp ). لذلك ، لا نقوم فقط بالتسلسل ، ولكننا نغير أيضًا حالة الخاصية.

تنطبق أفضل الممارسات shouldComponentUpdate للتعمق في مستوى واحد أثناء التركيب (أو useMemo مع الخطافات وهو أفضل بكثير).

@ webOS101 لماذا لا تنشئ حقل defaults حيث يمكنك وضع القيم الافتراضية لكل عنصر؟

ستكون القدرة على كتابة استعلامات MongoDB بمثابة تحسن كبير في اتساق البيانات ، وأود أن أرى هذا يعمل في قاعدة شفرة Rocket.Chat الخاصة بنا.

أحاول كتابة كائن بقيم يمكن الوصول إليها إما عن طريق obj[namespace][key] أو obj[namespace + "/" + key] ، ولكن لا يمكنني رؤية typeParam لمفتاح ومفتاح مسافة الاسم ، اللذين يجب التصريح بهما ، لا يمكنني رؤية طريقة كتابته. أي function f<Nk, K>(namespacedkey:Nk, key:K) ... سيتعين كما دعا f("namespace/key", "key") والذي يسمح للمستخدمين لاستخدام بطريق الخطأ مفتاح مختلف عن كل النهج.

سيكون هذا بالتأكيد أمرًا بالغ الأهمية لواجهة برمجة تطبيقات مكتبتي.

توجد قائمة بالخصائص المعرفة وقائمة باللاحقات الديناميكية. يجوز للمطور تحديد اللواحق الخاصة به ، والتي يجب أن تنشئ أزواجًا صالحة من: اسم الخاصية + اللاحقة. مثال:

| الملكية | لاحقة | سلسلة تم حلها |
| - | - | - |
| margin | md | marginMd |
| align | tablet | alignTablet |
| justify | anythingReally | justifyAnythingReally |

سيكون من الرائع أن يتم شحن هذا ، لذا يمكن للمكتبة أن تمنح أفضل DX مع تعريفات دقيقة للنوع. شكرا.

سيساعد هذا كثيرًا عند كتابة إجراءات إعادة الترتيب العالية - مما يجعلها آمنة تمامًا

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

export class Customer {
  public id?: number;
  public name?: string;
}
export interface WhereClause<T> {
}



md5-35a64b07076ca1f2908a04d038b70b0c



const where: WhereClause<Customer> = {
  name: 'John Smith',
};

تضمين التغريدة

export type WhereClause<T> = {
  [TColumn in keyof T]: string|number|SomeAggregateObject;
}

يمكنك بالفعل القيام بذلك.

weswigham أحاول تنفيذ ما اقترحته ، لكنني ما زلت أتلقى خطأ ، خاصة عندما يكون للفصل Customer طريقة عامة.

كمثال:

export class Customer {
  public id?: number;
  public name?: string;
  public getName(): string {
    return this.name;
  }
}

export type WhereClause<T> = {
  [TColumn in keyof T]: string|number|SomeAggregateObject;
};

const where: WhereClause<Customer> = {
  name: 'John Smith',
};

في السيناريو أعلاه ، تلقيت خطأ مفاده أن الخاصية getName مطلوبة من قبل WhereClause<Customer> .


تحرير: أحسبها:

export type WhereClause<T> = {
  [TColumn in keyof Partial<T>]: string|number|SomeAggregateObject;
}

شكرا للمساعدة!

ربما يجب أن تكتب

export type WhereClause<T> = {
  [TColumn in keyof T]?: string|number|SomeAggregateObject;
}

بدلاً من ذلك - يعد المُعدِّل ? على التعيين نفسه أكثر تعابير الاصطلاح من الاعتماد على المُعدِّل الذي يتدفق عبر الهدف keyof T .

نهج آخر لذلك هو أن يكون لديك:

interface ICustomer {
  public id?: number;
  public name?: string;
}

قم بتطبيقه على العميل وتمرير ICustomer كـ T بدلاً من ذلك.

akomm شكرًا على اقتراحك ، لكني أفضل تجنب وجود مكانين للحفاظ على خصائص الفصل. يمكنني بالتأكيد أن أتخيل أن شخصًا ما في المستقبل يضيف أو يزيل أو يعدل خاصية في فئة Customer وينسى واجهة ICustomer .

HillTravis النهج الآخر مناسب. ومع ذلك ، سيسمح برنامج WhereClause الخاص بك أيضًا باستعلام عن getName بجانب الاسم . لست متأكدًا مما إذا كان ذلك مطلوبًا. أو ، وتغلق الدائرة بعد ذلك ، تحتاج إلى مفتاح زيادة أثناء تعيين النوع :). بالتأكيد ، يمكنك البدء في الاختراق باستخدام الأدوية العامة المشروطة ووظائف التعيين إلى أبدًا أو ما شابه ذلك. لكن الحل ليس سريعًا جدًا وملاحظات الخطأ ليست مباشرة (getName signature غير قابل للتخصيص أبدًا).

مرحبًا بكم 👋
أرى أن هناك بعض المناقشات الجارية في هذه المسألة لبعض الوقت الآن (3 سنوات والعد! 🎂) وقد تم طرح بعض الاقتراحات وحالات الاستخدام المعقولة.
كما أنني لاحظت أن القضايا المماثلة قد تم دفعها إلى أسفل كأولوية للتراكم.

أود أن أفهم قليلاً كيف هو الوضع في هذا الشأن. هل هناك أي خطط للتعامل مع هذه الميزة في المستقبل المنظور؟
شكرا مقدما 🙇

مرحبا ، الكثير من الحب للطباعة!

أنا شخصياً أحب هذه الميزة للحصول على جميع المسارات الممكنة في POJO. شيء مثل:

/**
 * 
 * USELESS -- DONT USE !!
 * 
 * Need to be able to transform Paths<T[K]>
 * Something like: `${ K }.${ Paths<T[K]> }`
 * 
 */
type Paths<T extends object> = keyof T | {
    [K in keyof T]: T[K] extends object ? Paths<T[K]> /** `${ K }.${ Paths<T[K]> }` */ : K
}[keyof T];


interface A { aA: number }
interface B { aB: A, bB: number }
interface C { aC: A, bC: B, cC: number }

/**
 * Actualy resolve to
 */

type PathsA = Paths<A>; // 'aA';
type PathsB = Paths<B>; // 'aB' | 'bB' | 'aA';
type PathsC = Paths<C>; // 'aC' | 'bC' | 'cC' | 'aA' | 'aB' | 'bB';

/**
 * Would be nice to have
 */

type NiceToHavePathsA = Paths<A>; // 'aA';
type NiceToHavePathsB = Paths<B>; // 'aB' | 'bB' | 'aB.aA';
type NiceToHavePathsC = Paths<C>; // 'aC' | 'bC' | 'cC' | 'aC.aA' | 'bC.aB' | 'bC.bB' | 'bC.aB.aA';

هتافات.

إذا احتاج أي شخص إلى الإلهام للتغلب على هذا: تستخدم مكتبة knex المكتوبة الخاصة بي lambda للقيام بهذا العمل نوعًا ما. https://github.com/wwwouter/typed-knex

wwwouter نشكرك على المشاركة ، أشياء مفيدة هناك ؛ لسوء الحظ ، ما زال لا يحل حاجة شائعة جدًا لدي وهي أن أتمكن من ربط سلسلة حرفية =]

أي تحديث على هذا يا شباب؟

ربما يكون السؤال الأفضل - هل هناك أي شيء يمكن لأفراد المجتمع المهتمين بهذا الأمر أن يفعلوه للمساعدة؟

آخر نشاط رأيته في هذا كان على # 36623 ، من غير الواضح ما إذا كانت هناك أي خطة لتنفيذه أم لا. على الأقل يتم مناقشتها.

أحتاج أيضًا إلى دعم مفاتيح الكائنات التي تم إنشاؤها ديناميكيًا باستخدام تدوين النقطة.
على سبيل المثال.

// transforming
type nested = {a: { b: number }}
// into
type flat = {'a.b': number}

والسبب هو أن Google Firebase SDK لا يسمح إلا بتحديث الكائنات المتداخلة عبر تدوين النقطة (يشبه Firestore برنامج MongoDB).

ولكن لا توجد طريقة حاليًا بالنسبة لي لكتابة الحمولة بعد تحويلها إلى تدوين نقطي.

mesqueeb يمكنك إنشاء تجريد حول Firebase من أجل أمان النوع ، على سبيل المثال إنشاء function update<T, K1 extends keyof T, K2 extends keyof T[K]>(k1: K, k2: K2, val: T[K1][K2]): FirebaseUpdater => ({ [`${k1}.${k2}`]: val }) لذلك هناك حل بديل لـ userland مع نوع أمان جيد.

anilanar شكرا على الاقتراح!

لا أفهم كيف يمكنني استخدام هذا بالرغم من ذلك. لديّ "flattenObject" يمكنه أخذ أي كائن قد يجتازه أحد المطورين ، ثم يقوم بإرجاع الكائن "المسطح" بإشعار نقطي للدعائم.

أنا قادر على استخدام مجموعة أدوات ts لاسترداد كل "مسار" كمصفوفة من سلاسل المفاتيح في TS ، لكنني غير قادر على إعادة إنشاء النوع الذي يعيده ؛ وهو كائن ذو مسارات مسطحة كمفاتيح تدوين نقطي.

mesqueeb بشكل أساسي ، تحتاج إلى إنشاء الأنواع والوظائف الخاصة بك ويجب ألا تعتمد على أي أنواع تستخدمها للتفاعل مع Firebase. يتطلب ذلك بعض التقنيات المتقدمة مثل إنشاء أنواع جديدة أو أنواع فانتوم. المرجع: https://dev.to/busypeoples/notes-on-typescript-phantom-types-kg9 أو https://github.com/gcanti/newtype-ts

إذا كنت تفعل كل شيء ، فما عليك سوى نشره مفتوح المصدر كـ firebase-ts ، اكتب غلافًا آمنًا تمامًا حول firebase!

لم أستخدم قاعدة Firebase مطلقًا ، لقد قرأت قليلاً عن Firebase API لأعطيك فكرة. لم أفهم تمامًا كيف يعمل المرجع ، ولكن مع النهج التالي ، كل شيء ممكن إذا بذلت بعض الجهد فيه.

// This is incomplete, generics require some constraints
import * as TS from 'ts-toolbelt';

interface Database<T> {
  readonly _tag: unique symbol,
  readonly _type: T,
  readonly internal: firebase.database.Database,
}

interface Reference<T> {
  readonly _tag: unique symbol,
  readonly _type: T,
  readonly internal: firebase.database.Reference
}

export function database<T>(app: firebase.App): Database<T> {
  return unsafeCoerce({
    internal: firebase.database(app),
  });
}

export function reference<T, P>(database: Database<T>, path: P): Reference<TS.O.Path<T, P>> {
  return unsafeCoerce({
    internal: database.internal.ref(path.join('/')),
  });
}

export function update<T extends object>(
  ref: Reference<T>,
  values: Partial<T>,
): Promise<unknown> {
  return ref.internal.update(flattenObject(values));
}

function unsafeCoerce<A, B>(val: A): B {
  return val as unknown as B;
}
// Usage

interface MyFirebase {
  user: {
    id: string;
    name: string;
    company: {
      name: string;
    }
  }
}

const db = database<MyFirebase>(myApp);

// let's update company name.
update(reference(db, ['user', 'company']), { name: 'new company name' });

anilar سوف يعمل مضمنة . وفي مثالك الأخير ، تقوم بتمرير المسار إلى المرجع ، والذي سيسمح لك فقط بتغيير الخصائص في نفس الكائن الفرعي.

لنفترض أن كائن البيانات الخاص بك هو كما يلي:

interface User {
  homeAddress: {
    zipcode: string;
    valid: boolean;
  }
  workAddress: {
    zipcode: string;
    valid: boolean;
  }
}

وأنت تريد فقط تحديث صلاحية العناوين ، والتي في firestore (firebase مماثلة) يتم على النحو التالي

db.collection('users').doc(userId).update({'homeAddress.valid': false, 'workAddress.valid': false})

كيف تكتب غلاف TS لهذه الحالة؟

@ sk-

هذا لأنني اعتقدت أن Firebase يسمح بالتحديثات لمسار واحد فقط.

الاحتمال الآخر هو التحديثات الجزئية العميقة.

update(..., {
  homeAddress: {
    valid: false,
  },
  workAddress: {
     valid: false,
  }
}

يمكن أن تتعايش كل من واجهات برمجة التطبيقات.

أخبرني بلغة واحدة تحتوي على نوع الأمان للسلاسل المتسلسلة (ربما باستثناء وحدات الماكرو على مستوى التجميع في cpp / rust ؛ حتى ذلك الحين ، أشك في إمكانية تقسيم / تحليل السلاسل في وحدات الماكرو).

@ sk- إنه يعمل مع عدد لا حصر له من المستويات ، يحتوي TS على أنواع متكررة. يمكنك البحث عن محصلات المسار الآمن في ts-toolbelt. التحديثات الجزئية العميقة ممكنة أيضًا مع الأنواع العودية (تحقق من أنواع ramda).

anilanar نعم ، أنا على دراية بالأنواع العودية وكنت أستخدم TS-toolbelt لإنشاء أنواع أفضل لمتجر firestore. آسف لقد اختلطت عليّ بمثالك الأول ، لكن المثال الثاني الذي قدمته يوضح بالفعل كيفية استخدام Path . في الحقيقة لقد كنت أستخدمه بالفعل.

إنني أدرك أنه في بعض الأحيان يكون تصميم واجهة برمجة تطبيقات أفضل سيكون مثاليًا ، ولكن لسوء الحظ ، أبحرت هذه السفينة منذ فترة طويلة ولا يمكن (لن) إدخال تغييرات متقطعة. انظر على سبيل المثال: https://github.com/googleapis/nodejs-firestore/issues/1000 أو https://github.com/googleapis/nodejs-firestore/issues/1058

أنت محق في أنه كان من الأفضل لو قبل update كائن DeepPartial ، بدلاً من هذه النسخة الجزئية العميقة المسطحة. الشيء نفسه ينطبق على البعض الآخر من Apis.

كان اقتراحي هو تقديم مكتبة أخرى تغلف النسخة الأصلية ، مع وقت تشغيل طفيف ؛ والتي تتكون في الغالب من كود الغراء.

ملاحظتي هي أن المكتبات التي تم تصميمها حول ديناميكية جافا سكريبت لديها مشاكل أمان أكثر من النوع الذي يمكن حله من خلال ميزات TS الإضافية ومع تعريفات d.ts فقط ؛ إلا إذا قاموا بتعديل واجهات برمجة التطبيقات الخاصة بهم بطريقة غير متوافقة مع الإصدارات السابقة فقط لتلبية احتياجات مستخدمي TS. يشتكي مؤلفو مكتبة JS الذين لا يرغبون في الحفاظ على أنواع TS بالفعل من قواعد المستخدمين الخاصة بهم الذين يطلبون مثل هذه التغييرات على Twitter بشكل متكرر ؛ وصوت قوي ضد TS يتشكل في وسائل التواصل الاجتماعي.

يبدو أن المشكلات التي ربطتها لها نفس الطبيعة.

وعدت شركة TS بتقديم أفضل الجهود "للتشغيل البيني المجاني" مع JS ، ولكن تحقيق السلامة من الدرجة الأولى ليس مضمونًا أو موعودًا ليكون مجانيًا.

(أنت بارع في TS ومعظم الأشياء التي أقولها واضحة لك ، لكنني أكتب للمشاركين الآخرين في هذا الموضوع في انتظار ظهور هذه الميزة. آمل أن أساعد الآخرين والمشرفين على فهم ما يمكن أن يصنع JS / TS أفضل بشكل عام من دفع ميزات جديدة بقرارات نحوية متسرعة).

anilanar لقد

راجع https://github.com/googleapis/nodejs-firestore/blob/v0.8.0/src/reference.js#L452 -L461

منذ ذلك الحين تغيرت الطريقة وشهدت المكتبة 3 مطبات من الإصدارات الرئيسية. لذا ، أعتقد أن الأمر لا يتعلق فقط بـ JS vs TS ، ولكن أيضًا يتعلق بامتلاك الأدوات المناسبة للتعبير عن بعض الأفكار. أيضًا ، تطورت TS بسرعة كبيرة في السنوات الماضية (2.5 إلى 3.8 للمكتبة المعنية) ، لذلك ليس من السهل أيضًا تكييف قواعد الرموز الكبيرة ، خاصة تلك التي تحتوي على قاعدة مستخدمين أكبر.

ومع ذلك ، وبالنظر إلى أن مديري مكتبة JS و TS يكافحون في بعض الحالات لتلبية احتياجات جمهور معين ، فقد يكون من الرائع الحصول على قائمة بالأنماط والأنماط المضادة للسلامة النوعية.

احتجت مؤخرًا إلى شيء كهذا بنفسي ، كما وصفته في منشور SO:
https://stackoverflow.com/questions/61843772/typescript-infer-key-names-for-returned-object-from-function-input-argument

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

const [users, { append }] = useList(initialUsers)
const [admins, { append }] = useList(initialAdmins)

لن يعمل هذا بالطبع ، حيث سيتم تعريف append مرتين.

قد يكون أحد الحلول هو إعادة تسمية (أحدهما أو كليهما) مفاتيح الكائن المرتجعة أثناء التدمير.

ومع ذلك ، أود تنفيذه على النحو التالي:

const [users, { appendUsers }] = useList(initialUsers, "Users")
const [admins, { appendAdmins }] = useList(initialAdmins, "Admins")

لذلك قمت بإنشاء توقيع وظيفة TS هذا:

function useList<L extends any>({ initial = [], name = "" }: Options<L> = {}): [ L[], any ] {
   // ...
  return [
    items,
    {
      ["append" + name]: (newItem:L) => set([...items, newItem]),
    },
  ]
}

يعمل هذا ، ولكن لم يتم التحقق من نوع مفاتيح الكائن التي تم إرجاعها.

أود تحديد توقيع كل مفتاح معاد في الكائن ، وتوقيع قيمته.
على الأقل ، يجب تحديد عدد المفاتيح التي تم إرجاعها.

بدون الجزء الديناميكي ، الذي يأتي عبر وسيطة الوظيفة ، يمكن تغيير توقيع الإرجاع إلى:

[ L[], {
  append: ((T) => void)
} ]

لكني سأحتاج شيئًا مثل:

[ L[], {
  ["append" +  name]: ((T) => void)
} ]

أعتقد أن برنامج التحويل البرمجي TS يقبل وظيفة مولد المفتاح سيكون الأفضل والأكثر عالمية؟ شيء مثل

const key = (context, prefix): string => `${prefix}${context.args['name'].name}`
[ L[], {
  [key("append")]: ((T) => void)
} ]

سيتعين على وظيفة إنشاء المفاتيح إرجاع سلسلة ، واتخاذ سياق TS للوظيفة التي تم تحليلها حاليًا كوسيطة أولى لها. جميع الوسائط المتبقية هي تلك التي تم تمريرها عند استدعاء الوظيفة.

أي مؤشرات من قبل الناس في عمق محلل TS وسأجرب العلاقات العامة.

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

const [users, { append }] = useList(initialUsers)
const [admins, { append }] = useList(initialAdmins)

لن يعمل هذا بالطبع ، حيث سيتم تعريف append مرتين.

لا يمكنك فقط

const [users, { append: appendUsers }] = useList(initialUsers)
const [admins, { append: appendAdmins }] = useList(initialAdmins)

؟

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

andrevmatos بالطبع يستطيع. لكن لم يكن هذا هو الهدف من المثال التالي:

const [users, { append**Users** }] = useList(initialUsers, **"Users"**)
const [admins, { append**Admins** }] = useList(initialAdmins, **"Admins"**)

لا يمكنك فقط

const [users, { append: appendUsers }] = useList(initialUsers)
const [admins, { append: appendAdmins }] = useList(initialAdmins)

بالطبع ، هذا ما ذكرته في OP الخاص بي. وهذه هي الطريقة التي أفعلها الآن لأبقى في أمان.

pke لن أفعل العلاقات العامة ما لم تكن هناك مشكلة تطلب المساهمة - فهم ببساطة لن

لقد واجهت حالة استخدام حيث أحتاج إلى هذا. لدي موقف أحتاج فيه إلى تحويل مفاتيح كائن بسلسلة بسيطة فقط. حوّل ${keyof T} إلى ${keyof T}_changed .

بشكل عام ، يبدو أن أي كود يأخذ كائن تعريف وينشئ واجهة عادية للعمل مع هذا الكائن بناءً على مفاتيحه من المستحيل كتابته الآن. إنه لأمر مخز أننا لا نستطيع القيام بذلك حتى الآن! من المحتمل أن تكون جميع أنواع تطبيقات بناء النماذج / قواعد البيانات غير قابلة للطباعة بسبب هذا.

يمكنك الإدلاء ؟:

type StyleType = key of styles

const styles = {
  base: {},
  primary: {},
  seconary: {},
  primaryHover: {},
}

const MyButton = ({type} : {type: string}]) => (
  // ...
  return <Button style={
    ...styles.base,
    ...styles[props.type],
    hover && ...styles[type + "Hover" as StyleType],
  }/>
)

في كل مرة تستخدم فيها as تقول للطباعة: لا يمكنني إثبات أن هذا النوع صحيح ، لكن "Trust Me، I'm an Engineer ©".

أرغب في أن يكون لدي القدرة على التعبير عن نواياي باستخدام صيغة لغوية ولا أستخدم as في أي حال.

أعلم أنها كانت محاولة واهية لحل المشكلة.

لقد واجهت حالة استخدام حيث أحتاج إلى هذا. لدي موقف أحتاج فيه إلى تحويل مفاتيح كائن بسلسلة بسيطة فقط. حوّل ${keyof T} إلى ${keyof T}_changed .

أحتاج هذا للسبب نفسه تقريبًا. إضافة حقول "meta" المقابلة للحقول الموجودة في كائن. يعمل كودنا الحالي باستخدام القوالب غير الآمنة.

أرغب في أن أكون قادرًا على القيام بشيء مثل هذا:

interface WithMeta<T extends {}> {
    [K in keyof T]: T[K];
    [K+'_meta' for K in keyof T]: MetaInfo;
}

@ sk- على الرغم من أنني لا أفضل واجهات برمجة التطبيقات الموجهة للكائنات على تلك الوظيفية البحتة ، فإن TS حاليًا أقرب إلى لغات OOP من حيث ميزاتها. لذلك للحصول على أفضل الممارسات ، أود أن أنظر إلى لغات OOP الصوتية الأخرى ، دون أخذ أي اختصارات محددة لـ JS / الديناميكية.

لطالما كانت JS libs متوافقة مع فكرة أن المكتبة يجب أن تتمتع بسهولة فهم README.md ؛ يجب أن تكون هذه الوظيفة قادرة على التعامل مع كل شيء مع عشرات من الأحمال الزائدة إن لم يكن المئات (على سبيل المثال ، moment.js).

أقبل أن ترميز المعلومات القيمة في السلاسل كان نمطًا آخر تمارسه JS libs ؛ لكني أعتقد أنه يجب إلغاء هذه الممارسات لمجرد أنه حتى لو كان TS يدعم التلاعب بالسلسلة على مستوى النوع ، فإن التفاعل مع المجتمعات واللغات الأخرى سيكون أمرًا مروعًا.

لم يكن هوس بناء الجملة لدرجة ترميز المعلومات في سلاسل فكرة جيدة أبدًا (أسلوب الطباعة) ولن يكون أبدًا فكرة جيدة حتى يثبت أنه نهج عملي بلغات متعددة. هل يمكن لـ TS تحمل هذا الخطر بنفسه؟

هل قام شخص ما بعمل الخلفية النظرية على عمليات سلسلة ذات مستوى نوع وكيف سيعملون مع تعدد الأشكال؟ هل سيتطلب ذلك التراجع في محلل النوع؟ هل يدعم محلل نوع TS التراجع؟

نظرًا لأن السلاسل (نظريًا) قائمة من الأحرف ، هل يمكننا بطريقة ما إعادة استخدام آلات الكتابة التي لدينا بالفعل للمصفوفات / المجموعات ذات السلاسل؟

أعلم أن كل من يشاهد هذا الموضوع يحاولون حل مشاكلهم الحقيقية في العالم الحقيقي لمشروعاتهم في العالم الحقيقي ، لكن هذه المشاريع ستكون موجودة لمدة عامين ربما. من المحتمل أن تظل اللغة موجودة لفترة أطول ، ضع ذلك في الاعتبار.

تضمين التغريدة

هل سيتطلب ذلك التراجع في محلل النوع؟ هل يدعم محلل نوع TS التراجع؟

أعتقد أنه كذلك - لقد قام TS بتغيير التعيين الداخلي (في 3.9) إلى:

/* <strong i="10">@internal</strong> */
const enum TypeMapKind {
  Simple,
  Array,
  Function,
  Composite,
  Merged,
}

/* <strong i="11">@internal</strong> */
type TypeMapper =
  | { kind: TypeMapKind.Simple, source: ts.Type, target: ts.Type }
  | { kind: TypeMapKind.Array, sources: readonly ts.Type[], targets: readonly ts.Type[] | undefined }
  | { kind: TypeMapKind.Function, func: (t: ts.Type) => ts.Type }
  | { kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper };

اعتادت أن تكون وظيفة بسيطة لتعيين A-> B ، ولكن يبدو أنها تقوم الآن ببناء الخرائط بطريقة أكثر تفصيلاً وتنظيمًا. من الممكن أن يجعل هذا العمل إجراء الطفرات أسهل ، لأن التعيينات متكررة. للوهلة الأولى يجب أن تكون هذه "قابلة للعكس" إذا كان العكس B-> A مطلوبًا.

إذا كشف فريق TS عن القدرة على معالجة الارتباطات / التعيينات و / أو "إعادة الربط" أثناء التحويل (المكونات الإضافية) ، فمن الممكن أن يتم تنفيذ هذه المشكلة (زيادة المفاتيح) في أرض المستخدم.

يبدو أن هناك عددًا هائلاً من المشكلات التي تم وضع علامة عليها باعتبارها نسخة مكررة من هذه المشكلة. يقترح العديد منهم ميزة يمكنني تسميتها "الأنواع المتسلسلة" والتي ، في رأيي ، لا يغطيها هذا الاقتراح على الإطلاق.

يشير عنوان هذه المشكلة إلى أنه يجب تمكين هذه الميزة فقط أثناء تعيين النوع ، مما يجعل العديد من _ "الأنواع المتسلسلة" _ مستحيلة. على سبيل المثال ، بدون الانتقال إلى أنواع regex الأكثر تعقيدًا ، قد تكون الأنواع البسيطة مثل هذه مفيدة للغاية:

// Helper Types
type Hex = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F";
type Numeric = "0" | "1" | "2" | "3" | "4" | "5" |  "6" | "7" | "8" | "9";

// Concatenated Types
type Color = "#" + Hex + Hex + Hex + Hex + Hex + Hex | "#" + Hex + Hex + Hex;
type Email = string + "@" + string + "." + string;
type ZipCode = Numeric + Numeric + Numeric + Numeric + Numeric;

إذا كنت تريد أن تصاب بالجنون حقًا ، فقد يكون عامل التكرار مفيدًا أيضًا ، ويختصر مثال اللون إلى

type Color = "#" + Repeat<Hex, 6> | "#" + Repeat<Hex, 3>

أو شيء مشابه (أدرك أن هذا يخالف المعيار المعتاد لأنواع الأدوات المساعدة ، لأن "6" ليس نوعًا محددًا ، لذا فإن مجرد التمسك بالتكرار يدويًا أمر جيد). عامل تكرار آخر يمكن أن يكون

type ZipCode = Numeric * 5

والتي يمكن أن تقدم خيار _ "تكرار لانهائي" _ لشيء مثل

type FloatString = Numeric + "." + Numeric * *

ولكن سيكون من المعقول تمامًا حجز عامل التشغيل * لبعض الأفكار الساطعة الأخرى في المستقبل. بالطبع يمكن أيضًا استخدام * في Repeat<> thingymajig مما يجعل الوسيطة الثانية أقل شبهاً بالكتابة.

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

type Changed<T> = {          
   [ P in (keyof T) + "Changed"]?: Function;
}

مع إضافة الأجزاء الاختيارية ، يمكن أن تصبح الأنواع المفترسة أكثر فائدة لأغراض مثل

// Simple concatenated type
type RgbValue = Numeric * 2 | "1" + Numeric * 2 | "2" + ("0" | "1" | "2" | "3" | "4") + Numeric | "25" + ("0" | "1" | "2" | "3" | "4" | "5");
// Concatenated types with optional type
type AlphaValue = "0" | "1" | "0"? + "." + Numeric * *;
type Color = "#" + Hex * 6 | "#" + Hex * 3 | "rgba(" + (RgbValue + ", ") * 2 + RgbValue + (", " + AlphaValue)? + ")";

هذا ليس أجمل شيء ، لكنه سيجعل أشياء مثل هذه ممكنة على الأقل دون كتابة 16.5 مليون حالة.

كل هذا سيكون منطقيًا فقط على أنواع الأوتار ، تمامًا كما هو الحال مع الاقتراح الأصلي. شيء مثل type Weight = number + "kg" سيكون لطيفًا ، لكنني لا أرى كيف سيعمل نوع الأمان لأن أي number + string هو ببساطة string ، و لم يعد يتحدث بدقة عن أي شيء يتعلق برقم بعد الآن.

أنا شخصياً أعتقد أن ما تصفه هو طمس الخط الفاصل بين النوع الهيكلي والقيمة / التنسيق. في النهاية TS هو تحديد الهيكل. بينما يقوم TS بطمس الخط قليلاً باستخدام الأوتار ، إلا أنه يفعل ذلك فقط بقدر ما تتغلى الأوتار إلى ثابت.

يمكن تعريف "البنية" في الأمثلة الخاصة بك (وهي تعمل الآن) بشكل مثالي عبر Tuples.

type Hex = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F";
type Numeric = "0" | "1" | "2" | "3" | "4" | "5" |  "6" | "7" | "8" | "9";

type RGB = ["#", Hex, Hex, Hex, Hex, Hex, Hex];
type RGBA = ["#", Hex, Hex, Hex, Hex, Hex, Hex, Hex, Hex];

type Color = RGB | RGBA;
type Email = [string, "@", string, ".", string];
type ZipCode = [Numeric, Numeric, Numeric, Numeric, Numeric];

هناك أيضًا بعض الدعم للتكرار ، مع عامل تشغيل tuple rest:

type FloatString  = [Numeric, '.', ...Numeric[]];

على الرغم من أنه لا يتيح لك حاليًا القيام بما يلي ( يحاول الآخرون حله ):

type X3<T> = [T, T, T];
type X6<T> = [T, T, T, T, T, T];

type Color2 = ["#", ...X6<Hex>] | ["#", ...X3<Hex>] // error

في رأيي ، سيكون من الأفضل لك طلب النص المطبوع على الحروف لدعم السلاسل كمصفوفة مثل:

let a: FloatString = "1.23"; 

صالح - بهذه الطريقة ، على الرغم من أنك تعمل مع سلاسل - يمكن مطابقتها مع أنواع tuple. على سبيل المثال ، بالنسبة لأي سلسلة "1.23" يتم التعامل معها ضمنيًا على أنها Tuple ["1" ، "." ، "2" ، "3"] أثناء التقييم مقابل نوع Tuple آخر.

يبدو أن دعم السلاسل باعتبارها ArrayLike فكرة رائعة. تعتبر Tuples وحدها في هذه الحالة اختراقًا أكثر من أي شيء آخر. ['#' ,'0', '1', '2', '3', '4', '5'] أقل قابلية للقراءة من مجرد تعريف مجموعة أو فئة RGB ثم تحويلها إلى سلسلة CSS.

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

ملاحظة: لقد عثرت على هذه المشكلة وأريد حلاً مناسبًا أيضًا.

في غضون ذلك ، بالنسبة للأشخاص الذين يعانون من مشاكل في السلاسل ، وجدت طريقة بديلة للتفكير في هذا الأمر مع حراس النوع لمشكلتي:

type pet = "dog" | "cat" | "default";
const intensity: Record<pet, number> = { dog: 5, cat: 9, default: 12 };

const isPet = (s: string): s is pet => intensity[s as pet] !== undefined;

function getIntensityFromLabel(label: string): number {
  if (isPet(label)) return intensity[label];

  const [reversedDirectionLabel] = label.split("Reversed");
  if (isPet(reversedDirectionLabel))
    return intensity[reversedDirectionLabel] * -1;
  return intensity.default;
}

getIntensityFromLabel("dogReversed"); // -5
getIntensityFromLabel("dog"); // 5
getIntensityFromLabel("iLoveCake"); // 12

يجب أن يكون الرمز واضحًا ذاتيًا ، ولكن النقطة المهمة هي أنه في وظيفتي ، والتي ستحتاج إلى نوع معزز ، أستخدم نوعًا أكثر مرونة (سلسلة) ، ثم استخدم حراس الكتابة للحصول على ما أريد.

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

أعتقد أن الحد الأدنى من الميزات المطلوبة هنا والتي تبدو معقولة جدًا بالنسبة لي هي نوع السلسلة الحرفية. أعتقد أن أي نوع من التلاعب الحرفي معقد للغاية.
لذلك بشكل عام فقط:

type a = 'hhh';
type b = 'ggg';
type ab = a + b; // hhhggg

بالطبع مع دعم عام 😄

اكتب Email = [سلسلة ، "@" ، سلسلة ، "." ، سلسلة] ؛

سأكون على ما يرام مع ذلك ، وسيعمل:

email = "[email protected]"
Type '"[email protected]"' is not assignable to type '[string, "@", string, ".", string]'.ts(2322)

متى ستتم إضافة هذه الميزة ؟، إنها ضرورية جدًا بالنسبة لي.

أود أيضًا أن أرى شيئًا كهذا. في الوقت الحالي ، يتعذر على TypeScript التعامل مع أي رمز يقوم بإنشاء وظائف في وقت التشغيل بأسماء وظائف ديناميكية "تشبه القالب". على سبيل المثال ، الكود الذي يأخذ تسمية / مفتاح مثل "المستخدمون" وينشئ وظائف لـ fetchUsers() ، selectUsers() ، selectIsUsersLoading() ، إلخ.

حاليًا ، الطريقة الوحيدة الممكنة للتعامل معها هي التحديد الجاد للواجهات لكل طريقة ديناميكية ، لكل تسمية / مفتاح متغير. وهو أمر غير قابل للتنفيذ بشكل واضح.

الحل لهذه الحالة هو أن تكون واجهة برمجة التطبيقات مثل api.users.fetch() ، بدلاً من api.fetchUsers() . هذا مدعوم بشكل معقول من خلال آلات TS الحالية ، وأجد أنه أكثر نظافة في التنفيذ ، على أي حال. (أعلم أنه ليست كل واجهات برمجة التطبيقات قادرة / مستعدة لإجراء هذا النوع من التغييرات العاجلة)

Retsam صحيح ، من الواضح أن هذا سيكون الخيار المفضل. ولكن في الحالات التي لا يمكنك فيها إجراء هذا التغيير المفاجئ أو لا تتحكم في مكتبة تعمل بهذه الطريقة ، فإن تغيير واجهة برمجة التطبيقات ليس خيارًا.

تم تنفيذ هذا الاقتراح الآن في # 40336.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات