ジュニアフロントエンドのテストケースを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 .








All Articles