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:
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'

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:
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
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.
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
:
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;
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'
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'
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'

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'
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;
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,
Panggilan kami dari api getNews
mengembalikan Janji , dan "Janji" ini memiliki jenis tertentu, yang juga dapat kami uraikan:
interface INewsResponse { status: number;
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'
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'
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;
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'
, .
:
: . 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'
, 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'
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'
, .

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 .