العمل مع React Hooks و TypeScript

نشرت: 2022-03-11

تم تقديم الخطافات إلى React في فبراير 2019 لتحسين قابلية قراءة الكود. لقد ناقشنا بالفعل خطافات React في المقالات السابقة ، لكننا هذه المرة ندرس كيفية عمل الخطافات مع TypeScript.

قبل الخطافات ، كانت مكونات React لها نكهتان:

  • الطبقات التي تتعامل مع الدولة
  • الوظائف التي يتم تحديدها بالكامل من خلال الدعائم الخاصة بهم

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

ما هي خطافات React؟

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

ما هي خطافات React؟

ولكن مع نمو الكود ، تميل المكونات الوظيفية إلى التحول إلى مكونات حاوية.

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

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

 // put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }

هناك فائدة كبيرة لهذا - الترميز باستخدام TypeScript كان رائعًا مع Angular ولكنه منتفخ مع React. ومع ذلك ، فإن تشفير React hooks باستخدام TypeScript يعد تجربة ممتعة.

TypeScript مع رد فعل قديم

تم تصميم TypeScript بواسطة Microsoft واتبعت Angular path عندما طوّرت React Flow ، والذي بدأ الآن يفقد قوته. كانت كتابة فئات React باستخدام TypeScript ساذجًا مؤلمة للغاية لأن مطوري props اضطروا إلى كتابة كل من الخاصيتين state على الرغم من أن العديد من المفاتيح كانت متشابهة.

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

 interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }

لكن تخيل أن QuotationPage ستطلب الآن من الخادم معرفًا : على سبيل المثال ، عرض الأسعار 678 الذي قدمته الشركة. حسنًا ، هذا يعني أن QuotationProps لا تعرف هذا الرقم المهم - إنه لا يغلف عرض الأسعار بالضبط . يجب أن نعلن عن المزيد من التعليمات البرمجية في واجهة QuotationProps:

 interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }

نقوم بنسخ جميع السمات ولكن المعرف في نوع جديد. جلالة الملك. هذا يجعلني أفكر في Java القديمة ، والتي تضمنت كتابة مجموعة من DTOs. للتغلب على ذلك ، سنزيد معرفتنا في TypeScript لتجاوز الألم.

فوائد TypeScript مع الخطافات

باستخدام الخطافات ، سنتمكن من التخلص من واجهة QuotationState السابقة. للقيام بذلك ، سنقسم QuotationState إلى جزأين مختلفين من الدولة.

 interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

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

المكونات ذات الخطافات كلها وظائف. لذلك ، يمكننا كتابة نفس المكون بإرجاع نوع FC<P> المحدد في مكتبة React. تعلن الوظيفة صراحة عن نوع الإرجاع الخاص بها ، وتضبط على طول نوع الخاصيات.

 const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

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

هناك العديد من الأسباب التي تجعلك تتجنب TypeScript ، باستخدام React أم لا. ولكن إذا اخترت استخدامه ، فعليك بالتأكيد استخدام الخطافات أيضًا.

ميزات خاصة لـ TypeScript مناسبة للخطافات

في المثال السابق لخطافات React TypeScript ، ما زلت أمتلك سمة الرقم في QuotationProps ، لكن لا يوجد دليل حتى الآن على ماهية هذا الرقم في الواقع.

يوفر لنا TypeScript قائمة طويلة من أنواع الأدوات ، وستساعدنا ثلاثة منها في React عن طريق تقليل ضوضاء العديد من أوصاف الواجهات.

  • Partial<T> : أي مفاتيح فرعية لـ T.
  • Omit<T, 'x'> : جميع مفاتيح T باستثناء المفتاح x
  • Pick<T, 'x', 'y', 'z'> : بالضبط x, y, z مفاتيح من T

ميزات خاصة لـ TypeScript مناسبة للخطافات

في حالتنا ، نود Omit<Quotation, 'id'> لحذف معرف الاقتباس. يمكننا إنشاء نوع جديد على الطاير باستخدام type الكلمة الأساسية.

Partial<T> Omit<T> غير موجودة في معظم اللغات المكتوبة مثل Java ولكنها تساعد كثيرًا في الحصول على أمثلة مع النماذج في تطوير الواجهة الأمامية. يبسط عبء الكتابة.

 type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }

الآن لدينا اقتباس بدون معرف. لذلك ، ربما يمكننا تصميم عرض Quotation extends عرض Quotation PersistedQuotation . أيضًا ، سنحل بعض المشكلات المتكررة بسهولة if أو undefined . هل يجب أن نستدعي اقتباس المتغير ، على الرغم من أنه ليس الكائن الكامل؟ هذا خارج نطاق هذا المقال ، لكننا سنذكره لاحقًا على أي حال.

ومع ذلك ، نحن الآن على يقين من أننا لن ننشر كائنًا اعتقدنا أنه يحتوي على number . لا يؤدي استخدام Partial<T> إلى كل هذه الضمانات ، لذا استخدمها بحذر.

Pick<T, 'x'|'y'> هي طريقة أخرى للإعلان عن نوع أثناء التنقل دون عبء الاضطرار إلى إعلان واجهة جديدة. إذا كان أحد المكونات ، فما عليك سوى تحرير عنوان عرض الأسعار:

 type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>

أو فقط:

 function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}

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

الفوائد الأخرى لخطافات React

لطالما نظر فريق React إلى React وعاملها كإطار عمل وظيفي. لقد استخدموا الفئات حتى يتمكن المكون من التعامل مع حالته الخاصة ، والآن الخطافات كتقنية تسمح للدالة بتتبع حالة المكون.

 interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }

هذه حالة يكون فيها استخدام Partial آمنًا واختيارًا جيدًا.

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

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

من الواضح أن هذا أفضل لقابلية الاختبار - فبعض الوظائف تتعامل مع JSX ، والبعض الآخر مع السلوك ، والبعض الآخر مع منطق الأعمال ، وما إلى ذلك.

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

لذا ، قراءة الكود أسهل. الكود الخاص بك ليس تدفقًا من الفئات / الوظائف / الأنماط ولكنه تدفق للوظائف. ومع ذلك ، نظرًا لأن وظائفك ليست مرتبطة بكائن ، فقد يكون من الصعب تسمية كل هذه الوظائف.

TypeScript لا يزال JavaScript

JavaScript ممتع لأنه يمكنك تمزيق التعليمات البرمجية الخاصة بك في أي اتجاه. باستخدام TypeScript ، لا يزال بإمكانك استخدام keyof للعب بمفاتيح الكائنات. يمكنك استخدام نقابات الكتابة لإنشاء شيء غير قابل للقراءة ولا يمكن الحفاظ عليه - لا ، لا أحب هؤلاء. يمكنك استخدام اسم مستعار للتظاهر بأن السلسلة هي UUID.

ولكن يمكنك القيام بذلك بأمان فارغ. تأكد من أن tsconfig.json لديه الخيار "strict":true . تحقق من ذلك قبل بدء المشروع ، أو سيكون عليك إعادة بناء كل سطر تقريبًا!

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

أيضا ، لا يزال بإمكانك ارتكاب أخطاء في وقت التشغيل! يعد TypeScript أبسط من Java ويتجنب مشاكل التباين / التناقض مع Generics.

في مثال Animal / Cat هذا ، لدينا قائمة حيوانات مطابقة لقائمة Cat. لسوء الحظ ، إنه عقد في السطر الأول وليس الثاني. ثم نضيف بطة إلى قائمة الحيوانات ، وبالتالي فإن قائمة القط خاطئة.

 interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }

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

أيضا ، هناك اقتراح لإضافة عقود التغاير والتناقض داخل وخارج.

خاتمة

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

ربما كان TypeScript هو الفائز الأكبر في عام 2019. لقد اكتسبت React لكنها تغزو أيضًا عالم النهاية الخلفية باستخدام Node.js وقدرتها على كتابة المكتبات القديمة بملفات تعريف بسيطة جدًا. إنه يدفن Flow ، على الرغم من أن البعض يذهب طوال الطريق مع ReasonML.

الآن ، أشعر أن الخطافات + TypeScript أكثر متعة وإنتاجية من Angular. لم أكن لأتصور ذلك قبل ستة أشهر.