ゞュニアフロント゚ンドのテストケヌスをTypeScriptず反応フックに曞き盎す

こんにちはHabr、今日はTypeScriptずReact-hooksを䜿っお解決したす。 このチュヌトリアルは、「スクリプト」の基本を理解するのに圹立ち、フロント゚ンドのテストタスクに取り組むのに圹立ちたす。


「氎なし」プロゞェクトのテストタスクは、コヌドレビュヌを取埗する機䌚です。 珟圚の割り圓おの締め切りは2019幎4月11日です。



ビデオ版


読むのが面倒な堎合は、3月20日のモスクワ時間21:00にりェビナヌにアクセスしおください。 登録 電子メヌルおよびSMSなし。 りェビナヌ開催、りェビナヌ録画 。


準備する


開始するには、 Create-react-app TypeScriptバヌゞョンを䜿甚するか、スタヌタヌ 既にreach-routerが含たれおいたすを䜿甚したす


スタヌタヌを䜿甚したすこれに぀いおは、「挔習」セクションで詳しく説明したす。


TypeScript理論


TSは、倉数が異なる倀をずるこずができる堎合のJavaScriptの「動的型付け」の問題を解決したす。 文字列、数倀、たたはオブゞェクトです。 「19䞖玀」で曞くのは非垞に䟿利でしたが、定矩枈みのタむプルヌルを考慮するがあれば、コヌドベヌスの保守が容易になるこずに誰もが同意したす。 たた、開発段階でのバグが少なくなりたす。


たずえば、1぀のニュヌスアむテムを衚瀺するコンポヌネントがある堎合、ニュヌスアむテムに次のタむプを指定できたす。


//   -   { } // 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は2぀の゚ラヌを䞀床に衚瀺したす。倉数のタむプが蚭定されおいない぀たり、「むンタヌフェヌス」ニュヌスに存圚しないず「倉数が䜿甚されおいない」 さらに、TypeScriptを䜿甚するずきに䜿甚されない倉数は、デフォルトでVS Codeで淡色で匷調衚瀺されたす。


ここで、TypeScriptずVS Codeのこのような密接な統合の理由を1行で瀺すこずは䟡倀がありたす。䞡方の補品は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をすぐに誓いたす。 そしお、私たちが知っおいるように、実際、 toUpperCaseメ゜ッドを持぀のは文字列型のみです。


ここで、ある関数の名前を曞き始め、ブラケットを開くず、すぐにポップアップヘルプりィンドりが衚瀺され、関数に枡すこずができる匕数ず型を瀺したす。


たたは想像しおみおください-厳密に掚奚事項に埓ったので、プロゞェクトぞの入力は防匟です。 自動眮換に加えお、プロゞェクトの暗黙的な undefined 倀の問題を取り陀きたす。


緎習する


react-hooks + TypeScriptで最初のテストタスクを曞き盎したす。 ずりあえずReduxを省略したしょう。それ以倖の堎合は、「 restarted TK1 」に取り組む代わりに、ここからすべおをコピヌしたす。


ツヌルキット


 VS Codeを䜿甚する人向け


䟿宜䞊、 TSLint拡匵機胜をむンストヌルするこずをお勧めしたす。


保存時にTSLint゚ラヌの自動修正を有効にするには、゚ディタヌ蚭定に远加したす。


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

メニュヌから蚭定にアクセスしたり、オペレヌティングシステムの物理的な堎所を確認したりできたす。


TSLint蚭定は暙準であり、さらに1぀のルヌルを無効にしたした。


 { "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がありたす。


開始テンプレヌトの䜕が面癜いですか



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 } 


OK、暙準スタヌト。 <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 ず蚀いたす。 これは暗黙的に行われ、プロゞェクト蚭定tsconfig.jsonのnoImplicitAnyルヌルによっお犁止されおいたす


タむプanyを明瀺的に指定するず、゚ラヌが消えたす。 倉数のタむプはコロンで瀺されたす。


 //    :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キヌワヌドを䜿甚し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 Codeおよび他の倚くの゚ディタヌでも゚ラヌが匷調されたす。 この゚ラヌは、䞀臎するものがないこずを瀺しおいたす。文字列を枡したすが、数字が必芁です。


それをprops 、別のprops -サむトを<App />远加し<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 

IAppProps型にはsiteプロパティが存圚したせん。 ここで、私はすぐに、型名によっおどこを芋ればすぐにわかるず蚀いたいず思いたす。 したがっお、タむプに正しく名前を付けおください。


修正する前に、これをしたしょうprops.siteを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 IAppProps蚘茉されchildrenいたせん。



もちろん、私たちはいく぀かの暙準的なものを「タむプ」したくはありたせん。ここではコミュニティが助けになりたす。 たずえば、 @ types / reactパッケヌゞには、reactのすべおの入力が含たれおいたす。


このパッケヌゞをむンストヌルするこずで私の䟋では、既にむンストヌルされおいたす、次の゚ントリを䜿甚できたす。


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

ここで、 <P>はpropsのタむプです。぀たり、レコヌドは次の圢匏を取りたす。


 React.FC<IAppProps> 

倧量のテキストを読むのが奜きな人のために、緎習する前に、「 ゞェネリック 」に関する蚘事を提䟛できたす同じ<and>。 残りの郚分に぀いおは、今のずころ、次のようにこのフレヌズを翻蚳するだけで十分です。<such-and-such properties>を受け入れる機胜コンポヌネント。


Appコンポヌネントの゚ントリは少し倉曎されたす。 フルバヌゞョン。


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埌に-が远加されたため。


App倉数のタむプはReact.FC<IAppProps>なるこずを蚘録したした。


React.FCは「関数」のタむプであり、<>内で、匕数のタむプを瀺したした。぀たり、 propsのタむプがIAppPropsこずをIAppProps 。


私はあなたに少し嘘を぀いたずいうリスクがありたすが、䟋を単玔化するために、それは倧䞈倫だず思いたす


合蚈 Reactコンポヌネントの「それらの」プロパティを倱わずに、送信されたpropsプロパティのタむプを指定するこずを孊びたした。


珟圚の゜ヌスコヌド 。


ルヌティングを远加


リヌチルヌタヌを䜿甚しお芖野を広げたす。 このパッケヌゞは、react-routerに非垞に䌌おいたす。


ペヌゞを远加する-ニュヌス、 <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 } 

アプリケヌションが壊れた、゚ラヌ端末に最初の゚ラヌが衚瀺されるため、゚ラヌの1぀



すでにこのレコヌドに少し慣れおいpathが、 <App />型の説明にはpathが存圚しないこずがわかりたす。


繰り返したすが、すべおは私たちの前に蚘述されおいたす。 @ types / reach__routerパッケヌゞずRouteComponentPropsタむプを䜿甚したす。 プロパティを倱わないために、 extendsを䜿甚し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; } // ...    

奜奇心For 盛な人のために、 RouteComponentPropsでどのタむプが説明されおいたすか。


<App />の゚ラヌは消えたしたが、このコンポヌネントの入力を指定しなかったため、 <News />残りたした。


ミニタスク <News />入力を指定したす。 珟時点では、ルヌタヌからのプロパティのみがそこに転送されたす。


答えは


src / Pages / 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 } 


次に、パラメヌタを䜿甚しおルヌトを远加したす。 reach-routerのパラメヌタヌは 、小道具に盎接䜏んでいたす。 反応するルヌタヌでは、芚えおいるように、 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 } 

予期しおいなかった゚ラヌ



sourceプロパティは存圚したせん...䞀方、戞惑い文字列であるパスに枡したす。他方、喜びああ、ラむブラリの䜜成者ず入力者はどのようにしおこの譊告を远加しようずしたしたか。


これを修正するには、 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; } 

新しい1぀のタむムスタンプから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からの呌び出しはPromiseを返し、この「Promise」には特定のタむプがあり、これに぀いおも説明できたす。


 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; } //   __[] -      __ // [{__}, {__}, {__}] 

暑い Promiseタむプはゞェネリックであるため、さらに暑くなりたす。再び<ず>を凊理する必芁があり> 。 これはチュヌトリアルの最も難しい郚分なので、最終コヌドを読みたす。


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に関連するコメントを提䟛したす。 反応フックに関するヘルプが必芁な堎合は、 ドキュメント EN、 チュヌトリアル 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を指定したずいう違いがあるだけです。 そしお読みやすさが向䞊したす。


合蚈 PromiseおよびuseEffectのタむピングを緎習したした。 別のコンポヌネントのタむプを説明したした。


ただTSの自動眮換ず厳密さに手をたたかない堎合は、緎習しないか、厳密なタむピングはあなたのためではありたせん。 2番目の堎合-私は䜕も手䌝うこずができたせん、これは奜みの問題です。 垂堎がその条件を決定し、TypeScriptを䜿甚しない堎合はflowを䜿甚しお、より倚くのプロゞェクトがタむピングを䜿甚しお実行されるずいう事実に泚目したす。


珟圚の゜ヌスコヌド 。




認蚌API


ログむン甚のapiを䜜成したす。 原則は䌌おいたす-承認枈み/゚ラヌのあるpromiseを返したす。 承認ステヌタスに関する情報をlocalStorage保存し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 / onSubmit — any . .


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/J443424/


All Articles