Menulis ulang test case untuk junior frontend ke TypeScript dan react-hooks

Hai Habr, hari ini kita akan bekerja dengan TypeScript dan React-hooks. Tutorial ini akan membantu Anda memahami dasar-dasar "skrip" dan akan membantu Anda mengerjakan tugas tes untuk front-end .


Tugas uji pada proyek "Tanpa Air" adalah kesempatan untuk mendapatkan tinjauan kode. Batas waktu untuk penugasan saat ini adalah 11 April 2019.



Versi video


Jika Anda terlalu malas untuk membaca, datanglah ke webinar pada 20 Maret pukul 21.00 waktu Moskow. Pendaftaran (tanpa email dan sms). Webinar diadakan, rekaman webinar.


Persiapan


Untuk memulai, Anda dapat mengambil versi TypeScript buat-reaksi-aplikasi , atau gunakan starter saya (yang sudah termasuk router jangkauan)


Saya akan menggunakan starter saya (lebih lanjut tentang ini di bagian "Latihan").


Teori TypeScript


TS memecahkan masalah "pengetikan dinamis" dalam JavaScript, ketika variabel Anda dapat mengambil nilai yang berbeda. Sekarang sebuah garis, lalu sebuah angka, atau bahkan sebuah objek. Sangat mudah untuk menulis di "abad ke-19", namun, sekarang semua orang setuju bahwa jika Anda memiliki tipe yang sudah ditentukan sebelumnya (pertimbangkan aturan), maka basis kode lebih mudah dipertahankan. Dan ada lebih sedikit bug pada tahap pengembangan.


Misalnya, jika Anda memiliki komponen yang menampilkan satu item berita, kami dapat menentukan jenis berikut untuk item berita:


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

Dengan demikian, kami telah menetapkan jenis "statis" yang ketat untuk properti objek "berita" kami. Jika kami mencoba untuk mendapatkan properti yang tidak ada, TypeScript akan menampilkan kesalahan.


 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 } 


Juga, Visual studio Code dan editor tingkat lanjut lainnya akan menunjukkan kepada Anda kesalahan:



Secara visual, nyaman. Dalam kasus saya, VS Code menunjukkan dua kesalahan sekaligus: jenis variabel tidak disetel (artinya, tidak ada dalam berita "antarmuka" kami) dan "variabel tidak digunakan." Selain itu, variabel yang tidak digunakan saat menggunakan TypeScript disorot dalam warna pucat dalam Kode VS secara default.


Di sini bermanfaat untuk menunjukkan dalam satu baris alasan integrasi yang erat antara TypeScript dan VS Code: kedua produk tersebut adalah pengembangan Microsoft.


Apa lagi yang bisa TypeScript beri tahu kami segera? Berbicara dalam konteks variabel, itu saja. TS sangat kuat, dia mengerti apa itu.


 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> ) } 


Di sini, TypeScript segera bersumpah di properti yang tidak ada - toUpperCase number jenis. Dan seperti yang kita ketahui, memang, hanya tipe string yang memiliki metode toUpperCase () .


Sekarang bayangkan, Anda mulai menulis nama beberapa fungsi, buka braket dan editor segera menampilkan jendela bantuan pop-up, yang menunjukkan argumen apa dan jenis apa yang dapat diteruskan ke fungsi.


Atau bayangkan - Anda benar-benar mengikuti rekomendasi dan mengetik di proyek Anda anti peluru. Selain substitusi otomatis, Anda menyingkirkan masalah dengan nilai implisit ( undefined ) pada proyek.


Berlatih


Kami menulis ulang tugas tes pertama pada react-hooks + TypeScript. Untuk saat ini, mari kita hilangkan Redux , jika tidak alih-alih bekerja pada " restart TK # 1, " Anda cukup menyalin semuanya dari sini.


Toolkit


(bagi mereka yang menggunakan Kode VS )


Untuk kenyamanan, saya sarankan Anda menginstal ekstensi TSLint .


Untuk mengaktifkan perbaikan otomatis kesalahan TSLint pada saat penyimpanan, tambahkan pengaturan editor:


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

Anda dapat membuka pengaturan melalui menu, atau melihat di mana mereka hidup secara fisik di sistem operasi Anda.


Pengaturan TSLint adalah standar, ditambah saya menonaktifkan satu aturan.


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

Integrasi selesai!


Kami sedang menulis aplikasi


Kami akan berkenalan dengan hal-hal baru bagi kami saat bermain. Untuk memulai, klon cabang 1-mulai Anda atau sinkronkan dengan kode saya dalam kode Anda.


Semua file skrip jenis reaksi yang kita miliki memiliki ekstensi .tsx .


Apa yang menarik dalam template awal?


  • pra-komit kait (kami sudah membahas ini: teks , video )
  • TSLint (Penggantian ESLint )
  • file untuk memulai dan dependensi untuk ini dan langkah selanjutnya

Mari kita mulai dengan 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, mulai standar. Mari kita coba menambahkan beberapa properti ke <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" /> } 

Kami mendapatkan kesalahan:



(jika Anda tidak mendapatkan kesalahan, lalu periksa ketatnya pengaturan tsconfig.json Anda, harus ada aturan noImplicitAny)


Anda sudah menebak dari terjemahan teks kesalahan bahwa properti kami seharusnya tidak bertipe apa pun . Jenis ini dapat diterjemahkan sebagai "apa saja." Proyek kami memiliki aturan yang melarang keluaran implisit dari jenis ini.


- Inferensi tipe implisit?


- Tepat! TypeScript dapat menyimpulkan tipe variabel secara default dan ini mengatasi dengan baik. Ini disebut Type Inference.


Contoh:


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

Dalam kasus props - TS tidak dapat menentukan jenis variabel sebesar 100% dan karena itu mengatakan - biarkan itu menjadi (yaitu, ketik any ). Ini dilakukan secara implisit dan dilarang oleh aturan noImplicitAny di pengaturan proyek (tsconfig.json)


Kami dapat secara eksplisit menentukan jenis apa saja dan kesalahan akan hilang. Jenis variabel ditunjukkan oleh titik dua.


 //    :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> ) } 

Selesai, tidak ada kesalahan, proyek berfungsi, tetapi apa gunanya mengetik seperti itu ketika props bisa apa saja? Kami tahu pasti bahwa nama kami adalah . Aturan berikut dari ini:


Coba hindari mengetik

Ada saat-saat ketika any diperlukan dan ini normal, tetapi segera merupakan pukulan di bawah pinggang dengan pengetikan yang ketat.


Untuk menjelaskan jenis props , kami akan menggunakan kata kunci 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> ) } 

Coba ubah jenis name menjadi number dan kesalahan akan segera terjadi.



Selain itu, kesalahan juga akan ditekankan dalam VS Code (dan banyak editor lainnya). Kesalahan menunjukkan bahwa kami tidak memiliki kecocokan: kami melewatkan string, tetapi kami mengharapkan angka.


Kami props dan menambahkan properti lainnya - situs ke <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" /> } 

Mendapat kesalahan:


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

Properti site tidak ada dalam tipe IAppProps . Di sini, saya langsung ingin mengatakan bahwa dengan nama ketik kami segera mengerti ke mana harus mencari. Karena itu, sebutkan jenisnya dengan benar.


Sebelum kita memperbaikinya, mari kita lakukan ini: hapus paragraf yang props.site .


Kami mendapatkan teks kesalahan lain:



Di sini saya ingin mencatat hanya apa yang disimpulkan TS: site adalah jenis string (ini digarisbawahi dalam tangkapan layar).


Perbaiki:


 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" /> } 

Tidak ada kesalahan, tidak ada masalah.


Untuk bekerja dengan perutean, kita memerlukan rendering anak-anak. Mari kita maju dari diri kita sendiri dan mencoba menggambar "komponen anak".


 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 bersumpah, mereka mengatakan ini dan itu - children tidak dijelaskan dalam IAppProps .



Tentu saja, kami tidak ingin "melambangkan" beberapa hal standar, dan di sini komunitas datang untuk menyelamatkan, yang telah banyak mengetik sebelum kami. Misalnya, paket @ types / react berisi semua pengetikan untuk react.


Dengan menginstal paket ini (dalam contoh saya, ini sudah diinstal), kita dapat menggunakan entri berikut:


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

di mana <P> adalah jenis props kami, yaitu, catatan akan mengambil formulir.


 React.FC<IAppProps> 

Bagi mereka yang gemar membaca teks dalam volume besar, sebelum berlatih, saya dapat menawarkan artikel tentang " generik " (yang <and>). Untuk selebihnya, untuk saat ini, cukup kami menerjemahkan frasa ini seperti ini: komponen fungsional yang menerima <properti ini-dan-itu>.


Entri untuk komponen App akan sedikit berubah. Versi lengkap.


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> ) } 

Mari kita uraikan baris berikut ke dalam karakter:


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

- Mengapa tipe itu hilang setelah props ?


- Karena, setelah App - ditambahkan.


Kami mencatat bahwa variabel Aplikasi akan bertipe: React.FC<IAppProps> .


React.FC adalah jenis "fungsi", dan di dalam <> kami menunjukkan jenis argumen yang diperlukan, yaitu, menunjukkan bahwa props kami akan bertipe IAppProps .


(Ada risiko bahwa saya berbohong kepada Anda sedikit dalam hal, tetapi untuk menyederhanakan contoh, saya pikir tidak apa-apa)


Total: kami belajar menentukan jenis properti untuk props ditransmisikan, sambil tidak kehilangan properti "mereka" komponen-React.


Kode sumber saat ini.


Tambahkan Routing


Kami akan menggunakan reach-router untuk memperluas wawasan kami. Paket ini sangat mirip dengan react-router.


Tambahkan halaman - Berita (Berita), bersihkan <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 } 

Aplikasi telah rusak, sebuah kesalahan (salah satu kesalahan, karena terminal menunjukkan yang pertama):



Kami sudah terbiasa dengan catatan ini sedikit, dan kami memahami darinya bahwa path tidak ada dalam deskripsi tipe untuk <App /> .


Sekali lagi, semuanya dijelaskan sebelum kita. Kami akan menggunakan paket @ types / reach__router dan tipe RouteComponentProps . Agar tidak kehilangan properti kami, kami akan menggunakan 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; } // ...    

Bagi yang penasaran, tipe apa yang dijelaskan dalam RouteComponentProps .


Kesalahan di <App /> menghilang, tetapi tetap di <News /> , karena kami tidak menentukan pengetikan untuk komponen ini.


Teka-teki mini: tentukan pengetikan untuk <News /> . Saat ini, hanya properti dari router yang ditransfer di sana.


Jawabannya adalah:


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


Kami melanjutkan dan menambahkan rute dengan parameter. Parameter dalam router jangkauan langsung di alat peraga. Di react-router, seperti yang Anda ingat, mereka hidup di 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 } 

Kesalahan yang tidak kami duga:



Properti sumber tidak ada ... Di satu sisi, kebingungan: kami meneruskannya ke jalur, yang merupakan string, di sisi lain, kegembiraan: Ah, bagaimana para penulis perpustakaan dan mengetik mencoba menambahkan peringatan ini kepada kami.


Untuk memperbaikinya, kami akan menggunakan salah satu opsi : memperluas dari RouteComponentProps dan menentukan properti source opsional. Opsional, karena mungkin tidak ada di URL kami.


TypeScript menggunakan tanda tanya untuk menunjukkan properti opsional.


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 (pada saat yang sama kami akan Russify navigasi)


 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 } 


Total : kami belajar mengetikkan komponen yang terlibat dalam rotasi.


Kode sumber saat ini.




Mari kita bekerja dengan kait dan terus mengetik


Saya mengingatkan Anda bahwa tugas kami adalah mengimplementasikan tugas pengujian , tetapi tanpa Redux.


Saya menyiapkan cabang untuk memulai langkah ini dengan perutean, formulir login yang tidak berfungsi dan halaman yang diperlukan.



Unduh berita


Berita adalah berbagai objek.


Sajikan berita kami:


 { 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'), }, 

Mari kita segera menulis model (jenis berita):


src / model / news.ts (ekstensi .ts )


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

Dari yang baru - cap waktu menunjukkan jenis Date .


Bayangkan panggilan data kami:


 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 } 

Panggilan kami dari api getNews mengembalikan Janji , dan "Janji" ini memiliki jenis tertentu, yang juga dapat kami uraikan:


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

Apakah panas? Sekarang akan lebih panas, karena tipe Promise adalah generik, kita harus berurusan dengan < dan > . Ini adalah bagian paling sulit dari tutorial, jadi kami membaca kode terakhir:


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> } 

Istirahat asap.


Tampilkan berita


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 } 

Kode memberikan komentar yang berhubungan dengan TypeScript. Jika Anda memerlukan bantuan dengan reaksi-kait, Anda dapat membaca di sini: dokumentasi (EN), tutorial (RU).


Tugas: menulis komponen <NewsItem /> yang akan menampilkan berita. Ingatlah untuk menentukan jenis yang benar. Gunakan model INewsItem .


Hasilnya mungkin terlihat seperti ini:



Solusinya ada di bawah ini.


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 } 

Pertanyaannya adalah mengapa kami mendeskripsikan antarmuka dengan cara ini (komentar dalam kode [1] dan [2]). Bisakah kita menulis:


 React.FC<INewsItem> 

Jawabannya ada di bawah ini.


.


.


.


Bisa, karena kita menyampaikan berita di properti data , yaitu, kita harus menulis:


 React.FC<{ data: INewsItem }> 

Yang kami lakukan, hanya dengan perbedaan yang kami tentukan interface , dalam kasus properti lain ditambahkan ke komponen. Dan keterbacaan lebih baik.


Total: Kami berlatih mengetik untuk Janji dan menggunakan Efek. Menjelaskan tipe untuk komponen lain.


Jika Anda masih tidak bertepuk tangan karena substitusi dan kekakuan otomatis TS, maka Anda tidak berlatih atau mengetik ketat bukan untuk Anda. Dalam kasus kedua - Saya tidak bisa membantu dengan apa pun, ini masalah selera. Saya menarik perhatian Anda pada kenyataan bahwa pasar menentukan kondisinya dan semakin banyak proyek yang hidup dengan pengetikan, jika tidak dengan TypeScript, kemudian dengan aliran .


Kode sumber saat ini.




Auth api


Tulis api untuk login. Prinsipnya serupa - kami mengembalikan promise dengan jawaban yang diizinkan / kesalahan. Kami akan menyimpan informasi tentang status otorisasi di localStorage ( banyak yang menganggap ini sebagai pelanggaran keamanan yang mencolok, saya sedikit di belakang perselisihan dan tidak tahu bagaimana akhirnya ).


Dalam aplikasi kami, login adalah sekelompok nama pengguna dan kata sandi (keduanya adalah string), kami akan menjelaskan modelnya:


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 } 

, . , "".






Kesimpulan


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


, — .


" " telegram youtube ( 11 2019).


Terima kasih atas perhatian anda! , :




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


All Articles