TypeScript Praktis. Bereaksi + Redux

Saya tidak mengerti bagaimana Anda biasanya hidup tanpa pengetikan yang ketat. Apa yang kamu lakukan Debet sepanjang hari?

Saat ini, pengembangan aplikasi front-end modern lebih rumit daripada tingkat hello world , di mana tim bekerja (komposisi yang berubah secara berkala), membuat tuntutan tinggi pada kualitas basis kode. Untuk menjaga tingkat kualitas kode di tingkat yang tepat, kami di tim frontgroup #gost tetap terkini dan tidak takut untuk menerapkan teknologi modern yang menunjukkan manfaat praktisnya dalam proyek perusahaan dari berbagai skala .


Pengetikan statis dan manfaatnya pada contoh TypeScript telah banyak dibahas dalam berbagai artikel, dan oleh karena itu hari ini kita akan fokus pada tugas yang lebih banyak diterapkan yang dihadapi oleh pengembang front-end sebagai contoh tumpukan favorit tim kami (React + Redux).


"Aku tidak mengerti bagaimana biasanya kamu hidup tanpa pengetikan yang ketat. Apa yang kamu lakukan. Debet berhari-hari?" - penulis tidak dikenal oleh saya.


"tidak, kami menulis tipe sepanjang hari" - kolega saya.


Saat menulis kode dalam TypeScript (selanjutnya dalam teks tumpukan subjek akan tersirat), banyak mengeluh bahwa mereka harus menghabiskan banyak waktu menulis jenis secara manual. Contoh yang baik menggambarkan masalah adalah fungsi konektor connect dari react-redux . Mari kita lihat kode di bawah ini:


 type Props = { a: number, b: string; action1: (a: number) => void; action2: (b: string) => void; } class Component extends React.PureComponent<Props> { } connect( (state: RootStore) => ({ a: state.a, b: state.b, }), { action1, action2, }, )(Component); 

Apa masalahnya di sini? Masalahnya adalah bahwa untuk setiap properti baru yang disuntikkan melalui konektor, kita harus menggambarkan jenis properti ini dalam tipe umum properti komponen (Bereaksi). Bukan pekerjaan yang sangat menarik, Anda berkata, Anda masih ingin dapat mengumpulkan jenis properti dari konektor menjadi satu jenis, yang kemudian "menghubungkan" sekali ke jenis umum properti komponen. Saya punya kabar baik untuk Anda. TypeScript memungkinkan Anda melakukannya hari ini! Apakah kamu siap Ayo pergi!


Kekuatan TypeScript


TypeScript tidak tinggal diam dan terus berkembang (yang saya suka). Dimulai dengan versi 2.8, fungsi yang sangat menarik (tipe kondisional) muncul di dalamnya, yang memungkinkan pemetaan tipe berdasarkan ekspresi kondisional. Saya tidak akan membahas detailnya di sini, tetapi tinggalkan saja tautan ke dokumentasi dan masukkan sepotong kode sebagai ilustrasi:


 type TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object"; type T0 = TypeName<string>; // "string" type T1 = TypeName<"a">; // "string" type T2 = TypeName<true>; // "boolean" type T3 = TypeName<() => void>; // "function" type T4 = TypeName<string[]>; // "object" 

Bagaimana fungsi ini membantu dalam kasus kami. react-redux melihat deskripsi tipe pustaka react-redux , Anda dapat menemukan tipe InferableComponentEnhancerWithProps , yang bertanggung jawab untuk memastikan bahwa tipe properti yang diinjeksikan tidak jatuh ke dalam tipe eksternal properti komponen yang harus kita atur secara eksplisit saat membuat komponen. Tipe InferableComponentEnhancerWithProps memiliki dua parameter umum: TInjectedProps dan TNeedsProps . Kami tertarik pada yang pertama. Mari kita coba "tarik" jenis ini dari konektor ini!


 type TypeOfConnect<T> = T extends InferableComponentEnhancerWithProps<infer Props, infer _> ? Props : never ; 

Dan secara langsung menarik jenisnya pada contoh nyata dari repositori (yang dapat Anda tiru dan jalankan program pengujian di sana):


 import React from 'react'; import { connect } from 'react-redux'; import { RootStore, init, TypeOfConnect, thunkAction, unboxThunk } from 'src/redux'; const storeEnhancer = connect( (state: RootStore) => ({ ...state, }), { init, thunkAction: unboxThunk(thunkAction), } ); type AppProps = {} & TypeOfConnect<typeof storeEnhancer> ; class App extends React.PureComponent<AppProps> { componentDidMount() { this.props.init(); this.props.thunkAction(3000); } render() { return ( <> <div>{this.props.a}</div> <div>{this.props.b}</div> <div>{String(this.props.c)}</div> </> ); } } export default storeEnhancer(App); 

Dalam contoh di atas, kami membagi koneksi ke repositori (Redux) dalam dua tahap. Pada tahap pertama, kami mendapatkan komponen storeEnhancer orde lebih tinggi (alias InferableComponentEnhancerWithProps ) untuk mengekstraksi jenis properti InferableComponentEnhancerWithProps dari menggunakan TypeOfConnect helper TypeOfConnect kami dan selanjutnya menggabungkan (melalui persimpangan & jenis) jenis properti yang diperoleh dengan tipe properti asli dari komponen. Pada tahap kedua, kita cukup menghias komponen asli kita. Sekarang, apa pun yang Anda tambahkan ke konektor, itu akan secara otomatis jatuh ke dalam tipe properti komponen. Hebat, apa yang ingin kami raih!


Pembaca yang penuh perhatian memperhatikan bahwa pembuat tindakan (untuk singkatnya, kami menyederhanakan istilah untuk tindakan) dengan efek samping (pembuat tindakan unboxThunk ) menjalani pemrosesan tambahan menggunakan fungsi unboxThunk . Apa yang menyebabkan tindakan tambahan seperti itu? Mari kita perbaiki. Pertama, mari kita lihat tanda tangan dari tindakan seperti itu menggunakan contoh program dari repositori:


 const thunkAction = (delay: number): ThunkAction<void, RootStore, void, AnyAction> => (dispatch) => { console.log('waiting for', delay); setTimeout(() => { console.log('reset'); dispatch(reset()); }, delay); }; 

Seperti yang dapat dilihat dari tanda tangan, tindakan kami tidak segera mengembalikan fungsi target, tetapi pertama-tama fungsi perantara, yang diambil oleh redux-middleware untuk mengaktifkan efek samping pada fungsi utama kami. Tetapi ketika menggunakan fungsi ini dalam bentuk yang terhubung di properti komponen, tanda tangan dari fungsi ini berkurang, tidak termasuk fungsi perantara. Bagaimana menjelaskannya dalam tipe? Perlu fungsi konverter khusus. Sekali lagi TypeScript menunjukkan kekuatannya. Pertama, kami menjelaskan tipe yang menghapus fungsi antara dari tanda tangan:


 CutMiddleFunction<T> = T extends (...arg: infer Args) => (...args: any[]) => infer R ? (...arg: Args) => R : never ; 

Di sini, di samping tipe kondisional, kami menggunakan inovasi yang sangat baru dari TypeScript 3.0, yang memungkinkan kami untuk menyimpulkan tipe argumen fungsi (parameter parameter) yang berubah-ubah. Lihat dokumentasi untuk detailnya. Sekarang tinggal memotong kelebihan bagian dari tindakan kami dengan cara yang agak kaku:


 const unboxThunk = <Args extends any[], R, S, E, A extends Action<any>>( thunkFn: (...args: Args) => ThunkAction<R, S, E, A>, ) => ( thunkFn as any as CutMiddleFunction<typeof thunkFn> ); 

Melewati aksi melalui konverter seperti itu, kita mendapatkan tanda tangan yang kita butuhkan di output. Sekarang tindakan siap digunakan di konektor.


Jadi, melalui manipulasi sederhana, kami mengurangi pekerjaan manual kami saat menulis kode yang diketik di tumpukan kami. Jika Anda pergi sedikit lebih jauh, adalah mungkin untuk menyederhanakan dan redyuserov tipizirovanie game action, seperti yang kita lakukan di dalam redux-modus .


PS Saat menggunakan pengikatan aksi yang dinamis pada konektor melalui fungsi dan redux.bindActionCreators kita perlu menangani pengetikan yang lebih tepat dari utilitas ini (mungkin dengan menulis pembungkus kita sendiri).


Perbarui 0
Jika seseorang berpikir solusi ini nyaman, maka Anda bisa menyukainya sehingga utilitas tipe ditambahkan ke paket @types/react-redux .


Perbarui 1
Beberapa jenis lagi yang dengannya Anda tidak perlu secara eksplisit menentukan jenis alat peraga hock yang disuntikkan. Ambil hoki dan tarik keluar tipe-tipe itu:


 export type BasicHoc<T> = (Component: React.ComponentType<T>) => React.ComponentType<any>; export type ConfiguredHoc<T> = (...args: any[]) => (Component: React.ComponentType<T>) => React.ComponentType<any>; export type BasicHocProps<T> = T extends BasicHoc<infer Props> ? Props : never; export type ConfiguredHocProps<T> = T extends ConfiguredHoc<infer Props> ? Props : never; export type HocProps<T> = T extends BasicHoc<any> ? BasicHocProps<T> : T extends ConfiguredHoc<any> ? ConfiguredHocProps<T> : never ; const basicHoc = (Component: React.ComponentType<{a: number}>) => class extends React.Component {}; const configuredHoc = (opts: any) => (Component: React.ComponentType<{a: number}>) => class extends React.Component {}; type props1 = HocProps<typeof basicHoc>; // {a: number} type props2 = HocProps<typeof configuredHoc>; // {a: number} 

Pembaruan2
Jenis dari subjek sekarang di @types/react-redux ( ConnectedProps ).

Source: https://habr.com/ru/post/id431452/


All Articles