إعادة كتابة حالة اختبار الواجهة الأمامية إلى TypeScript وخطافات التفاعل

مرحباً هابر ، اليوم سننجح مع TypeScript و React-hooks. سيساعدك هذا البرنامج التعليمي في فهم أساسيات "البرنامج النصي" وسيساعدك على العمل في مهمة اختبار للواجهة الأمامية .


تعتبر مهام الاختبار على مشروع "بدون ماء" فرصة للحصول على مراجعة الكود. الموعد النهائي للمهمة الحالية هو 11 أبريل 2019.



نسخة الفيديو


إذا كنت كسولًا جدًا في القراءة ، فحضر إلى الويبينار يوم 20 مارس في الساعة 21:00 بتوقيت موسكو. التسجيل (بدون رسائل البريد الإلكتروني والرسائل القصيرة). عقد ندوة عبر الإنترنت ، وتسجيل ندوة عبر الإنترنت.


تدريب


للبدء ، يمكنك الحصول على إصدار TypeScript الخاص بتطبيق Create-react-app ، أو استخدام أداة بدء التشغيل الخاصة بي (والتي تتضمن بالفعل جهاز توجيه الوصول)


سوف أستخدم برنامج البداية الخاص بي (المزيد حول هذا الموضوع في قسم "التدريب").


نظرية TypeScript


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


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


//   -   { } // c  export interface INewsItem { id: number; // id  -   title: string; // title () -  text: string; // text ( ) -  link: string; // link () -  timestamp: Date; // timestamp () -    js } 

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


 import * as React from 'react' import { INewsItem } from '../models/news' //  "" interface INewsItemProps { data: INewsItem; //  ,   (    ) } const NewsItem: React.FC<INewsItemProps> = ({ data: { id, text, abracadabra }, //    , id  text - , abracadabra -  }) => { return ( <article> <div>{id}</div> <div>{text}</div> </article> ) } export { NewsItem } 


أيضًا ، سيظهر لك Visual Studio Code وغيره من المحررين المتقدمين خطأ:



بصريا ، مريحة. في حالتي ، يُظهر VS Code خطأين في آن واحد: لم يتم تعيين نوع المتغير (أي أنه غير موجود في أخبار "الواجهة" الخاصة بنا) و "المتغير غير مستخدم". علاوة على ذلك ، يتم تمييز المتغيرات غير المستخدمة عند استخدام TypeScript بلون باهت في VS Code بشكل افتراضي.


يجدر هنا الإشارة في سطر واحد إلى سبب هذا التكامل الوثيق بين TypeScript و VS Code: كلا المنتجين هما تطوير Microsoft.


ماذا يمكن أن يخبرنا برنامج TypeScript على الفور؟ التحدث في سياق المتغيرات ، هذا كل شيء. TS قوي للغاية ، إنه يفهم ما هو.


 const NewsItem: React.FC<INewsItemProps> = ({ data: { id, title, text } }) => { return ( <article> <div>{id.toUpperCase()}</div> {/* , ,  'number'   toUpperCase() */} <div>{title.toUpperCase()}</div> {/*    ! */} <div>{text}</div> </article> ) } 


هنا ، يقسم TypeScript على الفور على خاصية غير موجودة - toUpperCase من number النوع. وكما نعلم ، في الواقع ، أنواع السلاسل فقط لها طرق toUpperCase () .


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


أو تخيل - أنك اتبعت التوصيات بصرامة ، والكتابة على مشروعك مقاومة للرصاص. بالإضافة إلى الاستبدال التلقائي ، يمكنك التخلص من المشكلة باستخدام قيم ضمنية ( undefined ) في المشروع.


الممارسة


نعيد كتابة أول مهمة اختبار على رد فعل خطاف + TypeScript. الآن ، دعنا نتجاهل Redux ، وإلا فبدلاً من العمل على " إعادة تشغيل المعارف التقليدية رقم 1 " ، يمكنك فقط نسخ كل شيء من هنا.


أدوات


(لأولئك الذين يستخدمون رمز VS )


للراحة ، أنصحك بتثبيت ملحق TSLint .


لتمكين الإصلاح التلقائي لخطأ TSLint في وقت الحفظ ، أضف إعدادات المحرر:


 //  settings.json visual studio "editor.codeActionsOnSave": { "source.fixAll.tslint": true } 

يمكنك الوصول إلى الإعدادات من خلال القائمة ، أو معرفة مكان العيش الفعلي في نظام التشغيل الخاص بك.


إعدادات TSLint قياسية ، بالإضافة إلى أنني قمت بتعطيل قاعدة واحدة.


 { "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "linterOptions": { "exclude": [ "node_modules/**/*.ts", "src/serviceWorker.js" ] }, "rules": { "object-literal-sort-keys": false //        } } 

التكامل قد انتهى!


نحن نكتب الطلب


سوف نتعرف على أشياء جديدة بالنسبة لنا في سياق المسرحية. للبدء ، استنساخ فرع 1-start أو قم بالمزامنة مع الكود في الكود.


رد فعل جميع ملفات البرنامج النصي نوع لدينا التمديد .tsx .


ما هو المثير للاهتمام في قالب البداية؟


  • ربط مسبق (ناقشنا هذا بالفعل: نص ، فيديو )
  • TSLint (استبدال ESLint )
  • الملفات لبدء والتبعيات لهذا والخطوات التالية

لنبدأ مع src / App.tsx :


 import * as React from 'react' import './App.css' const App = () => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> </div> ) } const RoutedApp = () => { return <App /> } export { RoutedApp } 


حسنا ، بداية القياسية. دعونا نحاول إضافة بعض الخصائص إلى <App />


src / App.tsx


 const App = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> {/*   name  props */} <p>, {props.name}</p> </div> ) } //  name const RoutedApp = () => { return <App name="Max Frontend" /> } 

حصلنا على الخطأ:



(إذا لم تحصل على الخطأ ، ثم تحقق من دقة إعدادات tsconfig.json ، فينبغي أن تكون هناك قاعدة noImplicitAny)


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


- الاستنتاج الضمني؟


- بالضبط! TypeScript قادر على استنتاج نوع متغير افتراضيًا ويتواءم مع هذا البئر. وهذا ما يسمى نوع الاستدلال.


مثال:


 let x = 3 // TS ,  x   number,   number  " " (implicit) let x: number = 3 //   (explicit) ,  x   number //       , //       TS      

في حالة props - TS لا يمكن تحديد نوع المتغير بنسبة 100 ٪ ، وبالتالي يقول - فليكن ( ، اكتب any ). يتم ذلك ضمنيًا وهو محظور بموجب قاعدة noImplicitAny في إعدادات المشروع (tsconfig.json)


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


 //    :any //    "props: any"        //    const App = (props: any) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> </div> ) } 

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


حاول تجنب كتابة أي

هناك أوقات يكون فيها any ضروريًا وهذا أمر طبيعي ، لكنه على الفور ضربة تحت الخصر من خلال الكتابة الصارمة.


لوصف أنواع props ، سنستخدم الكلمة الأساسية interface :


 //  ,   IAppProps //    I,      TSLint  //  I     interface IAppProps { name: string; //  name   string } //        props const App = (props: IAppProps) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> </div> ) } 

حاول تغيير نوع name إلى number وسيحدث خطأ على الفور.



علاوة على ذلك ، سيتم التأكيد أيضًا على الخطأ في رمز VS (والعديد من المحررين الآخرين). يشير الخطأ إلى أننا لا نملك تطابقًا: نعبر سلسلة ، لكننا نتوقع رقماً.


سنقوم props وإضافة موقع آخر props إلى <App />


src / App.tsx


 interface IAppProps { name: string; } const App = (props: IAppProps) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> {/*  site */} <p>: {props.site}</p> </div> ) } //  site const RoutedApp = () => { return <App name="Max Frontend" site="maxpfrontend.ru" /> } 

حصلت على خطأ:


 Type error: Property 'site' does not exist on type 'IAppProps'. TS2339 

خاصية site غير موجودة في النوع IAppProps . هنا ، أود على الفور أن أقول أنه من خلال اسم النوع ، نفهم على الفور المكان الذي ننظر فيه. لذلك ، اسم الأنواع بشكل صحيح.


قبل أن props.site ، دعونا نفعل ذلك: احذف الفقرة التي props.site .


حصلنا على نص خطأ آخر:



أريد هنا أن أذكر فقط ما استنتجته TS: site هو نوع من string (يتم تسطير هذا في لقطة الشاشة).


إصلاح:


 interface IAppProps { name: string; site: string; //    } const App = (props: IAppProps) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> <p>: {props.site}</p> </div> ) } const RoutedApp = () => { return <App name="Max Frontend" site="maxpfrontend.ru" /> } 

لا أخطاء ، لا مشاكل.


للعمل مع التوجيه ، نحتاج إلى تقديم أطفال. دعنا نتقدم ونحاول رسم "عنصر فرعي".


 const App = (props: IAppProps) => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> ... //  <p>: {props.site}</p> {props.children} </div> ) } const Baby = () => { return <p> </p> } const RoutedApp = () => { return ( <App name="Max Frontend" site="maxpfrontend.ru"> <Baby /> </App> ) } 

يقسم TS ، كما يقولون فلان وفلان - لم children وصف children في IAppProps .



بالطبع ، لا نريد أن "نوصِّف" بعض الأشياء القياسية ، وهنا يأتي المجتمع إلى عملية الإنقاذ ، التي سبق أن كتبت الكثير أمامنا. على سبيل المثال ، تحتوي حزمة @ types / react على كل الكتابة للتفاعل.


عن طريق تثبيت هذه الحزمة (في المثال الخاص بي ، تم تثبيتها بالفعل) ، يمكننا استخدام الإدخال التالي:


 React.FunctionComponent<P>    React.FC<P> 

حيث <P> هي أنواع props الخاصة بنا ، أي أن السجل سيأخذ النموذج.


 React.FC<IAppProps> 

بالنسبة لأولئك الذين يعشقون قراءة كميات كبيرة من النص ، قبل التمرين ، يمكنني تقديم مقالة عن " الوراثة " (تلك <و>). بالنسبة للباقي ، في الوقت الحالي ، يكفي أن نترجم هذه العبارة مثل هذا: مكون وظيفي يقبل <مثل هذه الخصائص>.


سيتغير إدخال مكون التطبيق قليلاً. النسخة الكاملة.


src / App.tsx


 //    ,     //      React     React.XXX, //  XXX -    import * as React from 'react' //  ,    ,     //  @types/react //     interface IAppProps { name: string; site: string; } //   const App: React.FC<IAppProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <p></p> </nav> <p> </p> <p>, {props.name}</p> <p>: {props.site}</p> {props.children} </div> ) } const Baby = () => { return <p> </p> } const RoutedApp = () => { return ( <App name="Max Frontend" site="maxpfrontend.ru"> <Baby /> </App> ) } 

دعونا تحليل السطر التالي إلى أحرف:


 const App: React.FC<IAppProps> = props => { 

- لماذا اختفى النوع بعد props ؟


- لأنه ، بعد App - المضافة.


سجلنا أن متغير التطبيق سيكون من النوع: React.FC<IAppProps> .


React.FC هو نوع من "الوظيفة" ، وداخل <> أشرنا إلى نوع الوسيطة التي IAppProps الأمر ، وهذا يعني أن props بنا ستكون من النوع IAppProps .


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


المجموع: لقد تعلمنا تحديد نوع خصائص props المرسلة ، مع عدم فقد خصائص "تفاعلات" مكوناتها.


شفرة المصدر في الوقت الراهن.


إضافة التوجيه


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


أضف الصفحة - الأخبار (الأخبار) ، وقم بتنظيف <App /> .


src / pages / News.tsx


 import * as React from 'react' const News = () => { return ( <div className="news"> <p></p> </div> ) } export { News } 

src / App.tsx


 import * as React from 'react' //    reach-router import { Link, Router } from '@reach/router' import { News } from './pages/News' import './App.css' interface IAppProps { name: string; site: string; } const App: React.FC<IAppProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <Link to="/">Home</Link> <Link to="news">News</Link>{' '} </nav> <hr /> <p> {' '} : {props.name} | : {props.site} </p> <hr /> {props.children} </div> ) } //  Baby,  News.  app -  path const RoutedApp = () => { return ( <Router> <App path="/" name="Max Frontend" site="maxpfrontend.ru"> <News path="/news" /> </App> </Router> ) } export { RoutedApp } 

لقد تعطل التطبيق ، خطأ (أحد الأخطاء ، نظرًا لأن الجهاز يعرض الأول):



لقد اعتدنا بالفعل على هذا السجل قليلاً ، ونحن نفهم منه أن هذا path غير موجود في وصف النوع لـ <App /> .


مرة أخرى ، كل شيء موصوف أمامنا. سوف نستخدم حزمة @ types / RouteComponentProps ونوع RouteComponentProps . حتى لا تفقد فنادقنا ، سنستخدم extends .


 import * as React from 'react' //   RouteComponentProps  - // ts  ,     import { Link, RouteComponentProps, Router } from '@reach/router' import { News } from './pages/News' import './App.css' // extends       RouteComponentProps //     interface IAppProps extends RouteComponentProps { name: string; site: string; } // ...    

بالنسبة للفضوليين ، ما هي الأنواع الموضحة في RouteComponentProps .


اختفى الخطأ في <App /> ، لكنه بقي في <News /> ، حيث أننا لم نحدد الكتابة لهذا المكون.


لغز صغير: حدد الكتابة لـ <News /> . في الوقت الحالي ، يتم نقل الخصائص فقط من جهاز التوجيه هناك.


الجواب هو:


src / الصفحات / News.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' //      RouteComponentProps //       P ( React.FC<P> ) const News: React.FC<RouteComponentProps> = () => { return ( <div className="news"> <p></p> </div> ) } export { News } 


ننتقل ونضيف الطريق مع المعلمة. المعلمات في الوصول إلى جهاز التوجيه يعيش مباشرة في الدعائم. في جهاز التوجيه props.match ، كما تتذكر ، يعيشون في props.match .


src / App.tsx


 import * as React from 'react' import { Link, RouteComponentProps, Router } from '@reach/router' import { About } from './pages/About' import { News } from './pages/News' // ... () const RoutedApp = () => { return ( <Router> <App path="/" name="Max Frontend" site="maxpfrontend.ru"> <News path="/news" /> {/*     source */} <About path="/about/:source" /> </App> </Router> ) } export { RoutedApp } 

src / pages / About.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' const About: React.FC<RouteComponentProps> = props => { return ( <div className="about"> <p> about</p> {/*   source  */} <p>{props.source}</p> </div> ) } export { About } 

خطأ لم نكن نتوقعه:



الخاصية المصدر غير موجودة ... من ناحية ، الحيرة: نمررها إلى المسار ، وهي سلسلة ، من ناحية أخرى ، الفرح: آه ، حسنًا ، كيف حاول مؤلفو المكتبة والكتابة إضافة هذا التحذير إلينا.


لإصلاحها ، سوف نستخدم أحد الخيارات : التمديد من RouteComponentProps وتحديد خاصية source الاختيارية. اختياري ، لأنه قد لا يكون في عنوان URL الخاص بنا.


يستخدم TypeScript علامة استفهام للإشارة إلى خاصية اختيارية.


src / pages / About.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' interface IAboutProps extends RouteComponentProps { source?: string; //  source - ,      (    props.source  undefined) } const About: React.FC<IAboutProps> = props => { return ( <div className="about"> <p> about</p> <p>{props.source}</p> </div> ) } export { About } 

src / App.tsx (في نفس الوقت سنقوم بترويس الملاحة)


 import * as React from 'react' import { Link, RouteComponentProps, Router } from '@reach/router' import { About } from './pages/About' import { News } from './pages/News' import './App.css' interface IAppProps extends RouteComponentProps { name: string; site: string; } const App: React.FC<IAppProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <Link to="/"></Link> <Link to="news"></Link>{' '} <Link to="/about/habr"> habr</Link>{' '} </nav> <hr /> <p> {' '} : {props.name} | : {props.site} </p> <hr /> {props.children} </div> ) } const RoutedApp = () => { return ( <Router> <App path="/" name="Max Frontend" site="maxpfrontend.ru"> <News path="/news" /> <About path="/about/:source" /> </App> </Router> ) } export { RoutedApp } 


المجموع : لقد تعلمنا أن نوضح المكونات المشاركة في التناوب.


شفرة المصدر في الوقت الراهن.




دعونا نعمل مع السنانير ومواصلة الكتابة


أذكركم بأن مهمتنا هي تنفيذ مهمة اختبار ، ولكن بدون Redux.


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



تحميل الأخبار


الأخبار هي مجموعة من الأشياء.


تقديم أخبارنا:


 { id: 1, title: ' CRUD    React-hooks', text: '     CRUD-  ', link: 'https://maxpfrontend.ru/perevody/delaem-crud-prilozhenie-s-pomoschyu-react-hooks/', timestamp: new Date('01-15-2019'), }, 

لنكتب نموذجًا على الفور (أنواع الأخبار):


src / models / news.ts (ملحق .ts )


 export interface INewsItem { id: number; title: string; text: string; link: string; timestamp: Date; } 

من الجديد - الطابع الزمني أشار إلى نوع Date .


تخيل مكالمة بياناتنا:


 const fakeData = [ { id: 1, title: ' CRUD    React-hooks', text: '     CRUD-  ', link: 'https://maxpfrontend.ru/perevody/delaem-crud-prilozhenie-s-pomoschyu-react-hooks/', timestamp: new Date('01-15-2019'), }, { id: 2, title: '  React hooks', text: '      useState  useEffect ', link: 'https://maxpfrontend.ru/perevody/znakomstvo-s-react-hooks/', timestamp: new Date('01-06-2019'), }, { id: 3, title: '   Google Sign In', text: '   Google Sign In  ', link: 'https://maxpfrontend.ru/vebinary/avtorizatsiya-s-pomoschyu-google-sign-in/', timestamp: new Date('11-02-2018'), }, ] export const getNews = () => { const promise = new Promise(resolve => { resolve({ status: 200, data: fakeData, //    }) }) return promise //  promise } 

ترجع دعوتنا من api getNews إلى الوعد ، وهذا "الوعد" له نوع معين ، والذي يمكننا وصفه أيضًا:


 interface INewsResponse { status: number; //  -  data: INewsItem[]; // data -  ,    INewsItem [1] errorText?: string; // ,   errorText  ,     } // [1] ,   models      export interface INewsItem { id: number; title: string; text: string; link: string; timestamp: Date; } //   __[] -      __ // [{__}, {__}, {__}] 

هل الجو حار الآن سيكون أكثر سخونة ، لأن نوع الوعد يعد عامًا ، وسيتعين علينا مرة أخرى التعامل مع < و > . هذا هو الجزء الأكثر صعوبة في البرنامج التعليمي ، لذلك نقرأ الرمز النهائي:


src / api / News.ts


 import { INewsItem } from '../models/news' //    interface INewsResponse { //    __ status: number; data: INewsItem[]; errorText?: string; } const fakeData = [ //...  ] //    //    : // const myFunc = ():__ { return _ } // getNews -  ,    () ( ) //    Promise //   Promise -  generic,   : //  Promise<T>,  T -  ,     [1] //   ,  T ,   - INewsResponse export const getNews = (): Promise<INewsResponse> => { //  ,  [1] const promise = new Promise<INewsResponse>(resolve => { // [2] resolve({ status: 200, data: fakeData, }) }) return promise //    promise [2]  Promise<INewsResponse> } 

استراحة الدخان


عرض الأخبار


src / pages / News.tsx


 import * as React from 'react' import { RouteComponentProps } from '@reach/router' import { getNews } from '../api/news' import { NewsItem } from '../components/NewsItem' //    import { INewsItem } from '../models/news' const News: React.FC<RouteComponentProps> = () => { // useState -   ,     T //   ,  T -    INewsItem //  ,    ,     [] const [news, setNews] = React.useState<INewsItem[]>([]) // <-       React.useEffect(() => { getNews() .then(res => { setNews(res.data) }) .catch(err => { //   TSLint     console.log // , ""     // tslint:disable-next-line: no-console console.warn('Getting news problem', err) }) }, []) return ( <div className="news"> {news.map(item => ( <NewsItem data={item} key={item.id} /> ))} </div> ) } export { News } 

يعطي الكود تعليقات تتعلق بـ TypeScript. إذا كنت بحاجة إلى مساعدة في التعامل مع ردود الفعل ، يمكنك قراءة هنا: الوثائق (بالإنكليزية) ، والبرنامج التعليمي (RU).


المهمة: اكتب <NewsItem /> سيعرض الأخبار. تذكر أن تحدد النوع الصحيح. استخدم نموذج INewsItem .


قد تبدو النتيجة كما يلي:



الحل هو أدناه.


src / components / NewsItem.tsx


 import * as React from 'react' import { INewsItem } from '../models/news' interface INewsItemProps { data: INewsItem; // [1] } // [2] const NewsItem: React.FC<INewsItemProps> = ({ data: { title, text, timestamp, link }, }) => { return ( <article> <br /> <div> { <a href={link} target="_blank"> {title} </a> }{' '} | {timestamp.toLocaleDateString()} </div> <div>{text}</div> </article> ) } export { NewsItem } 

السؤال هو لماذا وصفنا الواجهة بهذه الطريقة (التعليقات في الكود [1] و [2]). هل يمكن أن نكتب فقط:


 React.FC<INewsItem> 

الجواب أدناه.


.


.


.


نظرًا لأننا نقوم بتمرير الأخبار في خاصية data ، فيجب أن نكتب:


 React.FC<{ data: INewsItem }> 

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


المجموع: لقد مارسنا الكتابة للوعد و useEffect. وصف النوع لمكون آخر.


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


شفرة المصدر في الوقت الراهن.




مصادقة api


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


في طلبنا ، تسجيل الدخول عبارة عن مجموعة من اسم المستخدم وكلمة المرور (كلاهما سلسلة) ، سنقوم بوصف النموذج:


src / models / user.ts


 export interface IUserIdentity { username: string; password: string; } 

src / api / auth.ts


 import { navigate } from '@reach/router' import { IUserIdentity } from '../models/user' //        //    data -  any ( ,  ) // ,    ,      , //    ,      ( ) interface IAuthResponse { status: number; data?: any; [1] errorText?: string; } // -,  -      //      IUserIdentity //    -  boolean  (true  false) const checkCredentials = (data: IUserIdentity): boolean => { if (data.username === 'Admin' && data.password === '12345') { return true } else { return false } } //   "-",    ,       //    ,      1  - data //    Promise<T>,  T -  IAuthResponse export const authenticate = (data: IUserIdentity): Promise<IAuthResponse> => { const promise = new Promise<IAuthResponse>((resolve, reject) => { if (!checkCredentials(data)) { reject({ status: 500, errorText: 'incorrect_login_or_password', }) } window.localStorage.setItem('tstz.authenticated', 'true') resolve({ status: 200, data: 'ok', //    -  string,     IAuthResponse [1] any  string }) }) return promise } //   ,    //  0  () //  true  false ( boolean) export const checkAuthStatus = (): boolean => { if (localStorage.getItem('tstz.authenticated')) { return true } else { return false } } //   ,  0  //    (    void) export const logout = (): void => { window.localStorage.removeItem('tstz.authenticated') navigate('/') //      url- (reach-router) } 

, .


:




: . useState . event onChange / onSubmitany . .


React.useState — , ( React.useState<T> )


, , , . , /profile ( navigate )


.


.


.


.


.


src/pages/Login.tsx


 import * as React from 'react' import { navigate, RouteComponentProps } from '@reach/router' import { authenticate } from '../api/auth' import { IUserIdentity } from '../models/user' //    ,     //      < > const Login: React.FC<RouteComponentProps> = () => { // useStaet  ,   useEffect - , //    ,     state const [user, setField] = React.useState<IUserIdentity>({ username: '', password: '', }) // ,    ( )  "" const [notification, setNotification] = React.useState<string>('') //  e (event) ,   <input /> //   : React.SyntheticEvent<HTMLInputElement> const onInputChange = (fieldName: string) => ( e: React.SyntheticEvent<HTMLInputElement> ): void => { setField({ ...user, [fieldName]: e.currentTarget.value, }) setNotification('') } //  e (event) ,    form //   : React.SyntheticEvent<HTMLFormElement> const onSubmit = (e: React.SyntheticEvent<HTMLFormElement>): void => { e.preventDefault() authenticate(user) .then(() => { navigate(`/profile`) //   profile }) .catch(err => { if (err.errorText) { setNotification(err.errorText) } else { // tslint:disable-next-line: no-console console.warn('request problem', err) } }) } return ( <> <h2>Login</h2> <form onSubmit={onSubmit}> {notification ? <p>{notification}</p> : null} <input type="text" value={user.username} onChange={onInputChange('username')} /> <input type="text" value={user.password} onChange={onInputChange('password')} /> <button>Login</button> </form> </> ) } export { Login } 

, TS — . , , JavaScript.


: useState event




TypeScript' , .


, reach-router , react-router. , , , .


src/components/common/Authenticated.tsx


 import * as React from 'react' import { Redirect, RouteComponentProps } from '@reach/router' import { checkAuthStatus } from '../../api/auth' //  noThrow    - https://reach.tech/router/api/Redirect const Authenticated: React.FC<RouteComponentProps> = ({ children }) => { return checkAuthStatus() ? ( <React.Fragment>{children}</React.Fragment> ) : ( <Redirect to="/login" noThrow={true} /> ) } export { Authenticated } 

src/App.tsx


 import * as React from 'react' import { Link, RouteComponentProps, Router } from '@reach/router' import { Authenticated } from './components/ommon/Authenticated' import { Home } from './pages/Home' import { Login } from './pages/Login' import { News } from './pages/News' import { Profile } from './pages/Profile' import { checkAuthStatus, logout } from './api/auth' import './App.css' const App: React.FC<RouteComponentProps> = props => { return ( <div className="container"> <h1>TZ #1 with hooks & TypeScript</h1> <nav> <Link to="/"></Link> <Link to="news"></Link>{' '} <Link to="profile"></Link>{' '} {checkAuthStatus() ? <button onClick={logout}></button> : null} </nav> {props.children} </div> ) } const RoutedApp = () => { return ( <Router> <App path="/"> <Home path="/" /> <Login path="/login" /> <News path="/news" /> <Authenticated path="/profile"> <Profile path="/" /> </Authenticated> </App> </Router> ) } export { RoutedApp } 

.


. type="password" .




, . "-", , , react-intl , react-i18next .


, . :


src/localization/formErrors.ts


 const formErrors = { ru: { incorrect_login_or_password: '      ', }, en: { incorrect_login_or_password: 'Incorrect login or password', }, } export { formErrors } 

<Login />


src/pages/Login.tsx


 import * as React from 'react' import { navigate, RouteComponentProps } from '@reach/router' import { authenticate } from '../api/auth' //   import { formErrors } from '../localization/formErrors' import { IUserIdentity } from '../models/user' //    const lang = 'ru' const Login: React.FC<RouteComponentProps> = () => { // ...  const onSubmit = (e: React.SyntheticEvent<HTMLFormElement>): void => { e.preventDefault() authenticate(user) .then(() => { navigate(`/profile`) }) .catch(err => { if (err.errorText) { //      (  ,  ru) setNotification(formErrors[lang][err.errorText]) } else { // tslint:disable-next-line: no-console console.warn('request problem', err) } }) } // ...  } export { Login } 

, .



TypeScript , , . , index signature ( , StackOverflow ).


 interface IFormErrors { [key: string]: { [key: string]: string, }; } const formErrors: IFormErrors = { ru: { incorrect_login_or_password: '      ', }, en: { incorrect_login_or_password: 'Incorrect login or password', }, } export { formErrors } 

, . , "".






الخاتمة


TypeScript. , TS . , , "one" "two" ( — union).


, — .


" " telegram youtube ( 11 2019).


شكرا لاهتمامكم! , :




CRA + TypeScript


TypeScript Playground


Understanding TypeScript's type notation ( Dr.Axel Rauschmayer)


Microsoft,


TSLint


tslint


tslint


tsconfig.json tslint.json


d.ts .ts


, staging .



react-typescript-samples LemonCode


:


es5 (!)


React v16


typescript-.


Microsoft. UI- Fabric . , github .

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


All Articles