Bekerja dengan React Hooks dan TypeScript

Diterbitkan: 2022-03-11

Hooks diperkenalkan ke React pada Februari 2019 untuk meningkatkan keterbacaan kode. Kita sudah membahas React hooks di artikel sebelumnya, tapi kali ini kita membahas bagaimana hook bekerja dengan TypeScript.

Sebelum hook, komponen React dulunya memiliki dua rasa:

  • Kelas yang menangani keadaan
  • Fungsi yang sepenuhnya ditentukan oleh props-nya

Penggunaan alami ini adalah untuk membangun komponen kontainer yang kompleks dengan kelas dan komponen presentasi sederhana dengan fungsi murni.

Apa Itu React Hook?

Komponen kontainer menangani manajemen status dan permintaan ke server, yang kemudian akan dipanggil dalam artikel ini efek samping . Negara akan disebarkan ke wadah anak-anak melalui alat peraga.

Apa Itu React Hook?

Namun seiring dengan perkembangan kode, komponen fungsional cenderung ditransformasikan sebagai komponen wadah.

Memutakhirkan komponen fungsional menjadi komponen yang lebih cerdas tidak terlalu menyakitkan, tetapi merupakan tugas yang memakan waktu dan tidak menyenangkan. Selain itu, membedakan presenter dan wadah dengan sangat ketat tidak diterima lagi.

Hooks dapat melakukan keduanya, sehingga kode yang dihasilkan lebih seragam dan memiliki hampir semua manfaat. Berikut adalah contoh menambahkan status lokal ke komponen kecil yang menangani tanda tangan kutipan.

 // put signature in local state and toggle signature when signed changes function QuotationSignature({quotation}) { const [signed, setSigned] = useState(quotation.signed); useEffect(() => { fetchPost(`quotation/${quotation.number}/sign`) }, [signed]); // effect will be fired when signed changes return <> <input type="checkbox" checked={signed} onChange={() => {setSigned(!signed)}}/> Signature </> }

Ada bonus besar untuk ini—coding dengan TypeScript sangat bagus dengan Angular tetapi membengkak dengan React. Namun, mengkodekan React hook dengan TypeScript adalah pengalaman yang menyenangkan.

TypeScript dengan Bereaksi Lama

TypeScript dirancang oleh Microsoft dan mengikuti jalur Angular ketika React mengembangkan Flow , yang sekarang kehilangan daya tariknya. Menulis kelas React dengan TypeScript naif cukup menyakitkan karena pengembang React harus mengetikkan props dan state meskipun banyak kunci yang sama.

Berikut adalah objek domain sederhana. Kami membuat aplikasi Quotation , dengan tipe Quotation , dikelola di beberapa komponen mentah dengan state dan props. Quotation dapat dibuat, dan status terkaitnya dapat berubah menjadi ditandatangani atau tidak.

 interface Quotation{ id: number title:string; lines:QuotationLine[] price: number } interface QuotationState{ readonly quotation:Quotation; signed: boolean } interface QuotationProps{ quotation:Quotation; } class QuotationPage extends Component<QuotationProps, QuotationState> { // ... }

Tapi bayangkan QuotationPage sekarang akan meminta server untuk id : misalnya, kutipan ke-678 yang dibuat oleh perusahaan. Nah, itu berarti QuotationProps tidak mengetahui angka penting itu—itu tidak membungkus Quotation dengan tepat . Kita harus mendeklarasikan lebih banyak kode di antarmuka QuotationProps:

 interface QuotationProps{ // ... all the attributes of Quotation but id title:string; lines:QuotationLine[] price: number }

Kami menyalin semua atribut kecuali id ​​dalam tipe baru. Hm. Itu membuat saya berpikir tentang Java lama, yang melibatkan penulisan banyak DTO. Untuk mengatasinya, kami akan meningkatkan pengetahuan TypeScript kami untuk melewati rasa sakit.

Manfaat TypeScript dengan Hooks

Dengan menggunakan kait, kita akan dapat menyingkirkan antarmuka QuotationState sebelumnya. Untuk melakukan ini, kami akan membagi QuotationState menjadi dua bagian negara bagian yang berbeda.

 interface QuotationProps{ quotation:Quotation; } function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

Dengan memisahkan status, kita tidak perlu membuat antarmuka baru. Jenis status lokal sering disimpulkan oleh nilai status default.

Komponen dengan kait semuanya berfungsi. Jadi, kita bisa menulis komponen yang sama yang mengembalikan tipe FC<P> yang didefinisikan di perpustakaan React. Fungsi secara eksplisit mendeklarasikan tipe pengembaliannya, mengatur sepanjang tipe props.

 const QuotationPage : FC<QuotationProps> = ({quotation}) => { const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); }

Jelas, menggunakan TypeScript dengan kait React lebih mudah daripada menggunakannya dengan kelas React. Dan karena pengetikan yang kuat adalah keamanan yang berharga untuk keamanan kode, Anda harus mempertimbangkan untuk menggunakan TypeScript jika proyek baru Anda menggunakan kait. Anda pasti harus menggunakan kait jika Anda menginginkan TypeScript.

Ada banyak alasan mengapa Anda bisa menghindari TypeScript, menggunakan React atau tidak. Tetapi jika Anda memilih untuk menggunakannya, Anda juga harus menggunakan pengait.

Fitur Spesifik TypeScript yang Cocok untuk Hooks

Dalam contoh React hooks TypeScript sebelumnya, saya masih memiliki atribut number di QuotationProps, tetapi belum ada petunjuk tentang apa sebenarnya angka itu.

TypeScript memberi kita daftar panjang jenis utilitas, dan tiga di antaranya akan membantu kita dengan React dengan mengurangi kebisingan dari banyak deskripsi antarmuka.

  • Partial<T> : setiap sub kunci dari T
  • Omit<T, 'x'> : semua kunci T kecuali kunci x
  • Pick<T, 'x', 'y', 'z'> : Tepat x, y, z kunci dari T

Fitur Spesifik TypeScript yang Cocok untuk Hooks

Dalam kasus kami, kami ingin Omit<Quotation, 'id'> untuk menghilangkan id kutipan. Kita dapat membuat tipe baru dengan cepat dengan kata kunci type .

Partial<T> dan Omit<T> tidak ada di sebagian besar bahasa yang diketik seperti Java tetapi banyak membantu untuk contoh dengan Formulir dalam pengembangan front-end. Ini menyederhanakan beban mengetik.

 type QuotationProps= Omit<Quotation, id>; function QuotationPage({quotation}:QuotationProps){ const [quotation, setQuotation] = useState(quotation); const [signed, setSigned] = useState(false); // ... }

Sekarang kami memiliki Quotation tanpa id. Jadi, mungkin kita bisa mendesain Quotation dan PersistedQuotation extends Quotation . Juga, kami akan dengan mudah menyelesaikan beberapa masalah berulang if atau undefined . Haruskah kita tetap memanggil kutipan variabel, meskipun itu bukan objek penuh? Itu di luar cakupan artikel ini, tetapi kami akan menyebutkannya nanti.

Namun, sekarang kami yakin bahwa kami tidak akan menyebarkan objek yang kami pikir memiliki number . Menggunakan Partial<T> tidak memberikan semua jaminan ini, jadi gunakanlah dengan bijaksana.

Pick<T, 'x'|'y'> adalah cara lain untuk mendeklarasikan tipe dengan cepat tanpa beban harus mendeklarasikan antarmuka baru. Jika sebuah komponen, cukup edit judul Quotation:

 type QuoteEditFormProps= Pick<Quotation, 'id'|'title'>

Atau hanya:

 function QuotationNameEditor({id, title}:Pick<Quotation, 'id'|'title'>){ ...}

Jangan menilai saya, saya penggemar berat Desain Berbasis Domain. Saya tidak malas sampai-sampai saya tidak ingin menulis dua baris lagi untuk antarmuka baru. Saya menggunakan antarmuka untuk mendeskripsikan nama Domain dengan tepat, dan utilitas ini berfungsi untuk kebenaran kode lokal, menghindari kebisingan. Pembaca akan tahu bahwa Quotation adalah antarmuka kanonik.

Manfaat Lain dari React Hooks

Tim React selalu memandang dan memperlakukan React sebagai kerangka kerja fungsional. Mereka menggunakan kelas sehingga komponen dapat menangani statusnya sendiri, dan sekarang kait sebagai teknik yang memungkinkan fungsi untuk melacak status komponen.

 interface Place{ city:string, country:string } const initialState:Place = { city: 'Rosebud', country: 'USA' }; function reducer(state:Place, action):Partial<Place> { switch (action.type) { case 'city': return { city: action.payload }; case 'country': return { country: action.payload }; } } function PlaceForm() { const [state, dispatch] = useReducer(reducer, initialState); return ( <form> <input type="text" name="city" onChange={(event) => { dispatch({ type: 'city',payload: event.target.value}) }} value={state.city} /> <input type="text" name="country" onChange={(event) => { dispatch({type: 'country', payload: event.target.value }) }} value={state.country} /> </form> ); }

Berikut adalah kasus di mana menggunakan Partial aman dan pilihan yang baik.

Meskipun suatu fungsi dapat dieksekusi berkali-kali, kait useReducer terkait hanya akan dibuat sekali.

Dengan mengekstrak fungsi peredam secara alami dari komponen, kode dapat dibagi menjadi beberapa fungsi independen alih-alih beberapa fungsi di dalam kelas, semuanya terkait dengan status di dalam kelas.

Ini jelas lebih baik untuk testabilitas—beberapa fungsi menangani JSX, yang lain dengan perilaku, yang lain dengan logika bisnis, dan seterusnya.

Anda (hampir) tidak membutuhkan Komponen Tingkat Tinggi lagi. Pola Render props lebih mudah ditulis dengan fungsi.

Jadi, membaca kode lebih mudah. Kode Anda bukan aliran kelas/fungsi/pola tetapi aliran fungsi. Namun, karena fungsi Anda tidak dilampirkan ke objek, mungkin sulit untuk memberi nama semua fungsi ini.

TypeScript Masih JavaScript

JavaScript menyenangkan karena Anda dapat merobek kode Anda ke segala arah. Dengan TypeScript, Anda masih dapat menggunakan keyof untuk bermain dengan kunci objek. Anda dapat menggunakan serikat tipe untuk membuat sesuatu yang tidak dapat dibaca dan tidak dapat dipelihara - tidak, saya tidak suka itu. Anda dapat menggunakan jenis alias untuk berpura-pura string adalah UUID.

Tetapi Anda dapat melakukannya dengan keamanan nol. Pastikan tsconfig.json Anda memiliki opsi "strict":true . Periksa sebelum memulai proyek, atau Anda harus memfaktorkan ulang hampir setiap baris!

Ada perdebatan tentang tingkat pengetikan yang Anda masukkan ke dalam kode Anda. Anda dapat mengetik semuanya atau membiarkan kompiler menyimpulkan jenisnya. Itu tergantung pada konfigurasi linter dan pilihan tim.

Juga, Anda masih dapat membuat kesalahan runtime! TypeScript lebih sederhana daripada Java dan menghindari masalah kovarians/kontravarians dengan Generics.

Dalam contoh Hewan/Kucing ini, kami memiliki daftar Hewan yang identik dengan daftar Kucing. Sayangnya, itu adalah kontrak di baris pertama, bukan yang kedua. Kemudian, kami menambahkan bebek ke daftar Hewan, jadi daftar Kucing salah.

 interface Animal {} interface Cat extends Animal { meow: () => string; } const duck = {age: 7}; const felix = { age: 12, meow: () => "Meow" }; const listOfAnimals: Animal[] = [duck]; const listOfCats: Cat[] = [felix]; function MyApp() { const [cats , setCats] = useState<Cat[]>(listOfCats); // Here the thing: listOfCats is declared as a Animal[] const [animals , setAnimals] = useState<Animal[]>(listOfCats) const [animal , setAnimal] = useState(duck) return <div onClick={()=>{ animals.unshift(animal) // we set as first cat a duck ! setAnimals([...animals]) // dirty forceUpdate } }> The first cat says {cats[0].meow()}</div>; }

TypeScript hanya memiliki pendekatan bivarian untuk obat generik yang sederhana dan membantu adopsi pengembang JavaScript. Jika Anda memberi nama variabel Anda dengan benar, Anda akan jarang menambahkan duck ke listOfCats .

Juga, ada proposal untuk menambahkan kontrak masuk dan keluar untuk kovarians dan kontravarians.

Kesimpulan

Saya baru-baru ini kembali ke Kotlin, yang merupakan bahasa yang baik dan diketik, dan rumit untuk mengetikkan obat generik yang kompleks dengan benar. Anda tidak memiliki kerumitan umum dengan TypeScript karena kesederhanaan pengetikan bebek dan pendekatan bivarian, tetapi Anda memiliki masalah lain. Mungkin Anda tidak menemui banyak masalah dengan Angular, dirancang dengan dan untuk TypeScript, tetapi Anda memilikinya dengan kelas React.

TypeScript mungkin adalah pemenang besar tahun 2019. TypeScript memperoleh React tetapi juga menaklukkan dunia back-end dengan Node.js dan kemampuannya untuk mengetik perpustakaan lama dengan file deklarasi yang cukup sederhana. Ini mengubur Flow, meskipun beberapa pergi jauh-jauh dengan ReasonML.

Sekarang, saya merasa bahwa hooks+TypeScript lebih menyenangkan dan produktif daripada Angular. Saya tidak akan berpikir itu enam bulan yang lalu.