TypeScript العملي. رد + استرجاع

لا أفهم كيف تعيش عمومًا دون كتابة دقيقة. ماذا تفعل الخصم طوال اليوم؟

في الوقت الحالي ، يعد تطوير أي تطبيق للواجهة الأمامية أكثر تعقيدًا من مستوى hello world ، الذي يعمل عليه الفريق (يتغير تكوينه بشكل دوري) ، مما يجعل الطلب مرتفعًا على جودة قاعدة الكود. للمحافظة على مستوى جودة الكود في المستوى المناسب ، نحن في فريق gostgroup #Gostgroup محدثون ولا نخشى استخدام التقنيات الحديثة التي تظهر مصلحتهم العملية في مشاريع الشركات بمختلف المقاييس .


تمت مناقشة الكتابة الثابتة وفوائدها على مثال TypeScript في العديد من المقالات ، وبالتالي سنركز اليوم على المزيد من المهام المطبقة التي يواجهها مطورو الواجهة الأمامية كمثال على المكدس المفضل لفريقنا (React + Redux).


"أنا لا أفهم كيف تعيش عمومًا دون كتابة دقيقة. ماذا تفعل. المدين لأيام متتالية؟" - المؤلف غير معروف لي.


"لا ، نكتب أنواعًا طوال اليوم" - زميلي.


عند كتابة تعليمة برمجية في TypeScript (المشار إليها فيما يلي في النص ، سيتم تضمين رصة الموضوع) ، ويشتكي كثيرون من أنه يتعين عليهم قضاء الكثير من الوقت في كتابة أنواع يدويًا. مثال جيد يوضح المشكلة هو وظيفة موصل connect من react-redux . دعنا نلقي نظرة على الكود أدناه:


 type Props = { a: number, b: string; action1: (a: number) => void; action2: (b: string) => void; } class Component extends React.PureComponent<Props> { } connect( (state: RootStore) => ({ a: state.a, b: state.b, }), { action1, action2, }, )(Component); 

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


قوة TypeScript


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


 type TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object"; type T0 = TypeName<string>; // "string" type T1 = TypeName<"a">; // "string" type T2 = TypeName<true>; // "boolean" type T3 = TypeName<() => void>; // "function" type T4 = TypeName<string[]>; // "object" 

كيف تساعد هذه الوظيفة في حالتنا. react-redux النظر في وصف أنواع مكتبة react-redux ، يمكنك العثور على نوع InferableComponentEnhancerWithProps ، المسؤول عن ضمان عدم تندرج أنواع الخصائص المحقونة في النوع الخارجي لخصائص المكون ، والذي يجب أن نضعه بوضوح عند إنشاء مثيل للمكون. يحتوي نوع InferableComponentEnhancerWithProps على معلمتين TInjectedProps : TInjectedProps و TNeedsProps . نحن مهتمون في الأول. دعونا نحاول "سحب" هذا النوع من هذا الرابط!


 type TypeOfConnect<T> = T extends InferableComponentEnhancerWithProps<infer Props, infer _> ? Props : never ; 

وسحب النوع مباشرة من مثال حقيقي من المستودع (يمكنك استنساخه وتشغيل برنامج اختبار هناك):


 import React from 'react'; import { connect } from 'react-redux'; import { RootStore, init, TypeOfConnect, thunkAction, unboxThunk } from 'src/redux'; const storeEnhancer = connect( (state: RootStore) => ({ ...state, }), { init, thunkAction: unboxThunk(thunkAction), } ); type AppProps = {} & TypeOfConnect<typeof storeEnhancer> ; class App extends React.PureComponent<AppProps> { componentDidMount() { this.props.init(); this.props.thunkAction(3000); } render() { return ( <> <div>{this.props.a}</div> <div>{this.props.b}</div> <div>{String(this.props.c)}</div> </> ); } } export default storeEnhancer(App); 

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


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


 const thunkAction = (delay: number): ThunkAction<void, RootStore, void, AnyAction> => (dispatch) => { console.log('waiting for', delay); setTimeout(() => { console.log('reset'); dispatch(reset()); }, delay); }; 

كما يتضح من التوقيع ، فإن عملنا لا يعيد الوظيفة المستهدفة على الفور ، ولكن أولاً الوسيطة ، التي تلتقط برامج إعادة redux-middleware لتمكين الآثار الجانبية في وظيفتنا الرئيسية. ولكن عند استخدام هذه الوظيفة في النموذج المتصل في خصائص المكون ، يتم تقليل توقيع هذه الوظيفة ، باستثناء الوظيفة الوسيطة. كيف تصفها في الأنواع؟ بحاجة الى وظيفة محول خاص. مرة أخرى يظهر TypeScript قوتها. أولاً ، نصف النوع الذي يزيل الدالة الوسيطة من التوقيع:


 CutMiddleFunction<T> = T extends (...arg: infer Args) => (...args: any[]) => infer R ? (...arg: Args) => R : never ; 

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


 const unboxThunk = <Args extends any[], R, S, E, A extends Action<any>>( thunkFn: (...args: Args) => ThunkAction<R, S, E, A>, ) => ( thunkFn as any as CutMiddleFunction<typeof thunkFn> ); 

بتمرير الإجراء من خلال هذا المحول ، نحصل على التوقيع الذي نحتاجه في الإخراج. الآن الإجراء جاهز للاستخدام في الموصل.


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


ملاحظة: عند استخدام الربط الديناميكي للإجراءات في الموصل من خلال الوظيفة و redux.bindActionCreators سنحتاج إلى العناية بالكتابة الأكثر ملاءمة لهذه الأداة المساعدة (ربما عن طريق كتابة برنامجنا الخاص).


تحديث 0
إذا اعتقد شخص ما أن هذا الحل مناسب ، فبإمكانك إعجابه بحيث تتم إضافة أداة الكتابة المساعدة إلى @types/react-redux .


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


 export type BasicHoc<T> = (Component: React.ComponentType<T>) => React.ComponentType<any>; export type ConfiguredHoc<T> = (...args: any[]) => (Component: React.ComponentType<T>) => React.ComponentType<any>; export type BasicHocProps<T> = T extends BasicHoc<infer Props> ? Props : never; export type ConfiguredHocProps<T> = T extends ConfiguredHoc<infer Props> ? Props : never; export type HocProps<T> = T extends BasicHoc<any> ? BasicHocProps<T> : T extends ConfiguredHoc<any> ? ConfiguredHocProps<T> : never ; const basicHoc = (Component: React.ComponentType<{a: number}>) => class extends React.Component {}; const configuredHoc = (opts: any) => (Component: React.ComponentType<{a: number}>) => class extends React.Component {}; type props1 = HocProps<typeof basicHoc>; // {a: number} type props2 = HocProps<typeof configuredHoc>; // {a: number} 

تحديث 2
النوع من الموضوع موجود الآن في @types/react-redux ( ConnectedProps ).

Source: https://habr.com/ru/post/ar431452/


All Articles