Ketika saya melihat file {domain} /selectors.js di proyek React / Redux besar yang saya kerjakan, saya sering melihat daftar penyeleksi redux yang besar seperti ini:
getUsers(state) getUser(id)(state) getUserId(id)(state) getUserFirstName(id)(state) getUserLastName(id)(state) getUserEmailSelector(id)(state) getUserFullName(id)(state) âĻ
Pada pandangan pertama, penggunaan penyeleksi tidak terlihat aneh, tetapi dengan pengalaman kita mulai memahami bahwa mungkin ada terlalu banyak penyeleksi. Dan sepertinya kita selamat sampai titik ini.

Redux dan penyeleksi
Mari kita lihat Redux. Apa dia, kenapa? Setelah membaca redux.js.org, kami memahami bahwa Redux adalah "wadah yang dapat diprediksi untuk menyimpan status aplikasi JavaScript"
Saat menggunakan Redux, kami disarankan untuk menggunakan penyeleksi, meskipun mereka opsional. Penyeleksi hanya pengambil untuk mendapatkan beberapa bagian dari seluruh negara, yaitu fungsi dari form (State) => SubState
. Biasanya kami menulis penyeleksi sehingga kami tidak dapat mengakses negara secara langsung, dan kemudian kami dapat menggabungkan atau memotret hasil penyeleksi ini. Kedengarannya masuk akal.
Sangat tenggelam dalam pemilih
Daftar pemilih yang saya kutip dalam pengantar artikel ini adalah karakteristik dari kode yang dibuat terburu-buru.
Bayangkan kami memiliki model Pengguna dan kami ingin menambahkan bidang email baru ke dalamnya. Kami memiliki komponen yang mengharapkan input firstName
dan lastName
, dan sekarang akan menunggu email
lain. Mengikuti logika dalam kode dengan penyeleksi, memperkenalkan bidang email baru, penulis harus menambahkan pemilih getUserEmailSelector
dan menggunakannya untuk meneruskan bidang ini ke komponen. Bingo!
Tapi apakah bingo? Dan jika kita mendapatkan pemilih lain, mana yang lebih rumit? Kami akan menggabungkannya dengan penyeleksi lain, dan mungkin kami akan sampai pada gambar ini:
const getUsers = (state) => state.users; const getUser = (id) => (state) => getUsers(state)[id]; const getUserEmailSelector = (id) => (state) => getUser(id)(state).email;
Pertanyaan pertama muncul: apa yang harus getUserEmailSelector
pemilih getUserEmailSelector
jika pemilih getUser
kembali tanpa undefined
? Dan ini adalah situasi yang mungkin - bug, refactoring, warisan - semuanya dapat mengarah padanya. Secara umum, tidak pernah menjadi tugas penyeleksi untuk menangani kesalahan atau memberikan nilai default.
Masalah kedua muncul dengan menguji penyeleksi tersebut. Jika kita ingin menutupinya dengan unit test, kita akan memerlukan data tiruan yang identik dengan data dari produksi. Kami harus menggunakan data tiruan seluruh negara bagian (karena negara bagian tidak dapat tidak konsisten dalam produksi) untuk pemilih ini saja. Ini, tergantung pada arsitektur aplikasi kita, bisa sangat tidak nyaman - menyeret data ke dalam pengujian.
Mari kita asumsikan bahwa kita menulis dan menguji pemilih getUserEmailSelector
seperti dijelaskan di atas. Kami menggunakannya dan menghubungkan komponen ke negara:
const mapStateToProps = (state, ownProps) => ({ firstName: getUserFirstName(ownProps.userId)(state), lastName: getUserLastName(ownProps.userId)(state), // email: getUserEmailName(ownProps.userId)(state), })
Mengikuti logika di atas, kami mendapatkan sekelompok penyeleksi yang berada di awal artikel.
Kami sudah terlalu jauh. Sebagai hasilnya, kami menulis pseudo-API untuk entitas Pengguna. API ini tidak dapat digunakan di luar konteks Redux karena membutuhkan pemeran status lengkap. Selain itu, API ini sulit diperluas - saat menambahkan bidang baru ke entitas Pengguna, kita harus membuat penyeleksi baru, menambahkannya ke mapStateToProps, menulis lebih banyak kode boilerplate.
Atau mungkin Anda harus mengakses bidang entitas secara langsung?
Jika masalahnya hanya kita memiliki terlalu banyak penyeleksi - mungkin kita hanya menggunakan getUser dan mengakses properti entitas yang kita butuhkan secara langsung?
const user = getUser(id)(state); const email = user.email;
Pendekatan ini memecahkan masalah penulisan dan mendukung sejumlah besar penyeleksi, tetapi menciptakan masalah lain. Jika kita perlu mengubah model Pengguna, kita juga perlu memeriksa semua tempat di mana user.email
( catatan penerjemah atau bidang lain yang kita ubah). Dengan sejumlah besar kode dalam proyek, ini bisa menjadi tugas yang sulit dan menyulitkan bahkan sedikit refactoring. Ketika kami memiliki pemilih, itu melindungi kami dari konsekuensi perubahan, karena memikul tanggung jawab bekerja dengan model dan kode menggunakan pemilih tidak tahu apa-apa tentang model.
Akses langsung dapat dimengerti. Tetapi bagaimana dengan menerima data yang dihitung? Misalnya, dengan nama pengguna lengkap, yang merupakan gabungan dari nama depan dan belakang? Perlu menggali lebih lanjut ...

Model domain menuju. Redux - Sekunder
Anda dapat melihat gambar ini dengan menjawab dua pertanyaan:
- Bagaimana kita mendefinisikan model domain kita?
- Bagaimana kita menyimpan data? (manajemen negara, untuk itu kami menggunakan redux * translator's note * bahwa layer persistence disebut dalam DDD)
Menjawab pertanyaan "Bagaimana kita mendefinisikan model domain" (dalam kasus kami, Pengguna), mari kita abstrak dari redux dan memutuskan apa "pengguna" itu dan API apa yang diperlukan untuk berinteraksi dengannya?
// api.ts type User = { id: string, firstName: string, lastName: string, email: string, ... } const getFirstName = (user: User) => user.firstName; const getLastName = (user: User) => user.lastName; const getFullName = (user: User) => `${user.firstName} ${user.lastName}`; const getEmail = (user: User) => user.email; ... const createUser = (id: string, firstName: string, ...) => User;
Akan bagus jika kita selalu menggunakan API ini dan menganggap model Pengguna tidak dapat diakses di luar file api.ts. Ini berarti bahwa kita tidak akan pernah beralih langsung ke bidang entitas sejak saat itu kode yang menggunakan API bahkan tidak tahu entitas mana yang memiliki bidang.
Sekarang kita bisa kembali ke Redux dan menyelesaikan masalah yang hanya berkaitan dengan negara:
- Tempat apa yang ditempati pengguna dalam artikel kami?
- Bagaimana seharusnya kita menyimpan pengguna? Daftar? Kamus (nilai kunci)? Bagaimana lagi?
- Bagaimana kita akan mendapatkan instance Pengguna dari negara? Haruskah memoisasi digunakan? (dalam konteks pemilih getUser)
API kecil dengan manfaat besar
Menerapkan prinsip berbagi tanggung jawab antara bidang subjek dan negara bagian, kami mendapatkan banyak bonus.
Model domain yang didokumentasikan dengan baik (Model pengguna dan API-nya) di file api.ts. Ini cocok untuk pengujian, sebagai tidak memiliki dependensi. Kita dapat mengekstrak model dan API ke perpustakaan untuk digunakan kembali di aplikasi lain.
Kita dapat dengan mudah menggabungkan fungsi API sebagai penyeleksi, yang merupakan keunggulan yang tak tertandingi dibandingkan akses langsung ke properti. Selain itu, antarmuka data kami sekarang mudah dipelihara di masa mendatang - kami dapat dengan mudah mengubah model Pengguna tanpa mengubah kode yang menggunakannya.
Tidak ada keajaiban terjadi dengan API, itu masih terlihat jelas. API menyerupai apa yang dilakukan dengan menggunakan penyeleksi, tetapi memiliki satu perbedaan utama: tidak memerlukan seluruh keadaan, tidak perlu mendukung status penuh aplikasi untuk pengujian lagi - API tidak ada hubungannya dengan Redux dan kode boilerplate-nya.
Alat peraga komponen telah menjadi lebih bersih. Alih-alih menunggu input Nama depan, nama belakang, dan email menjadi input, komponen menerima instance Pengguna dan secara internal menggunakan API-nya untuk mengakses data yang diperlukan. Ternyata kita hanya membutuhkan satu pemilih - getUser.
Ada manfaat untuk reduksi dan middleware dari API semacam itu. Inti dari manfaatnya adalah pertama-tama Anda bisa mendapatkan instance dari Pengguna, menangani nilai-nilai yang hilang di dalamnya, memproses atau mencegah semua kesalahan, dan kemudian menggunakan metode API. Ini lebih baik daripada menggunakan setiap bidang individu menggunakan penyeleksi dalam isolasi dari area subjek. Dengan demikian, Redux benar-benar menjadi "wadah yang dapat diprediksi" dan tidak lagi menjadi objek "ilahi" dengan pengetahuan tentang segalanya.
Kesimpulan
Dengan niat baik (baca di sini - penyeleksi) jalan menuju neraka sudah diaspal: kami tidak ingin mengakses bidang entitas secara langsung dan membuat penyeleksi terpisah untuk ini.
Meskipun gagasan penyeleksi itu sendiri bagus, terlalu sering membuatnya sulit untuk mempertahankan kode kami.
Solusi yang dijelaskan dalam artikel ini mengusulkan untuk menyelesaikan masalah dalam dua tahap - pertama menggambarkan model domain dan API-nya, kemudian berurusan dengan Redux (penyimpanan data, penyeleksi). Dengan cara ini Anda akan menulis kode yang lebih baik dan lebih kecil - Anda hanya perlu satu pemilih untuk membuat API yang lebih fleksibel dan skalabel.
Catatan Penerjemah
- Saya menggunakan kata negara, karena sepertinya kata itu telah dengan kuat memasuki perbendaharaan kata para pengembang berbahasa Rusia.
- Penulis menggunakan kata-kata hulu / hilir yang berarti "kode tingkat tinggi / tingkat rendah" (jika menurut Martin) atau "kode yang digunakan di bawah / kode di bawah ini yang menggunakan apa yang tertulis di atas", tetapi tidak benar untuk mengetahui cara menggunakan ini dalam terjemahan Karena itu, saya dapat menghibur diri sendiri dengan berusaha tidak mengecewakan akal sehat.
Saya dengan senang hati akan menerima komentar dan saran untuk koreksi di PM dan memperbaikinya.