Haruskah penyimpanan fluks, atau tindakan (atau keduanya) menyentuh layanan eksternal?

122

Jika toko mempertahankan statusnya sendiri dan memiliki kemampuan untuk memanggil layanan jaringan dan penyimpanan data dalam melakukannya ... dalam hal ini tindakannya hanyalah pelintas pesan bodoh,

-ATAU-

... haruskah penyimpanan menjadi penerima bodoh dari data yang tidak dapat diubah dari tindakan (dan tindakan adalah tindakan yang mengambil / mengirim data antara sumber eksternal? Simpan dalam contoh ini akan bertindak sebagai model tampilan dan akan dapat menggabungkan / memfilter mereka data sebelum menetapkan basis negara mereka sendiri pada data yang tidak dapat diubah yang diberi makan oleh tindakan.

Menurut saya, itu harus salah satu atau yang lain (bukan campuran keduanya). Jika ya, mengapa yang satu lebih disukai / direkomendasikan daripada yang lain?

plaxdan.dll
sumber
2
Posting ini mungkin membantu code-experience.com/…
Markus-ipse
Bagi mereka yang mengevaluasi berbagai implementasi pola fluks, saya sangat merekomendasikan untuk melihat Redux github.com/rackt/redux Toko diimplementasikan sebagai fungsi murni yang mengambil status saat ini dan mengeluarkan versi baru dari status itu. Karena mereka murni berfungsi, pertanyaan apakah mereka dapat memanggil layanan jaringan dan penyimpanan diambil dari tangan Anda: mereka tidak bisa.
plaxdan

Jawaban:

151

Saya telah melihat pola fluks diterapkan dengan dua cara, dan setelah melakukan keduanya sendiri (awalnya menggunakan pendekatan sebelumnya), saya percaya bahwa penyimpanan harus menjadi penerima data yang bodoh dari tindakan, dan bahwa pemrosesan penulisan yang tidak sinkron harus dilakukan di pencipta aksi. ( Pembacaan Async dapat ditangani secara berbeda .) Menurut pengalaman saya, ini memiliki beberapa manfaat, dalam urutan kepentingannya:

  1. Toko Anda menjadi sangat sinkron. Hal ini membuat logika penyimpanan Anda lebih mudah diikuti dan sangat mudah untuk diuji — cukup buat instance penyimpanan dengan beberapa status tertentu, kirimkan tindakan, dan periksa untuk melihat apakah status berubah seperti yang diharapkan. Lebih lanjut, salah satu konsep inti dalam fluks adalah untuk mencegah pengiriman bertingkat dan untuk mencegah beberapa pengiriman sekaligus; ini sangat sulit dilakukan ketika toko Anda melakukan pemrosesan asinkron.

  2. Semua pengiriman tindakan terjadi dari pembuat tindakan. Jika Anda menangani operasi asinkron di toko Anda dan ingin menjaga penangan tindakan toko Anda tetap sinkron (dan Anda harus untuk mendapatkan jaminan pengiriman tunggal flux), toko Anda perlu mengaktifkan tindakan SUKSES dan GAGAL tambahan sebagai respons terhadap asinkron pengolahan. Menempatkan kiriman ini dalam pembuat tindakan malah membantu memisahkan pekerjaan pembuat tindakan dan toko; Selain itu, Anda tidak perlu menggali logika toko Anda untuk mencari tahu dari mana tindakan akan dikirim. Tindakan asinkron yang khas dalam kasus ini mungkin terlihat seperti ini (ubah sintaksis dispatchpanggilan berdasarkan rasa fluks yang Anda gunakan):

    someActionCreator: function(userId) {
      // Dispatch an action now so that stores that want
      // to optimistically update their state can do so.
      dispatch("SOME_ACTION", {userId: userId});
    
      // This example uses promises, but you can use Node-style
      // callbacks or whatever you want for error handling.
      SomeDataAccessLayer.doSomething(userId)
      .then(function(newData) {
        // Stores that optimistically updated may not do anything
        // with a "SUCCESS" action, but you might e.g. stop showing
        // a loading indicator, etc.
        dispatch("SOME_ACTION_SUCCESS", {userId: userId, newData: newData});
      }, function(error) {
        // Stores can roll back by watching for the error case.
        dispatch("SOME_ACTION_FAIL", {userId: userId, error: error});
      });
    }

    Logika yang dapat diduplikasi di berbagai tindakan harus diekstraksi menjadi modul terpisah; dalam contoh ini, modul itu adalah SomeDataAccessLayer, yang menangani permintaan Ajax yang sebenarnya.

  3. Anda membutuhkan lebih sedikit pembuat tindakan. Ini bukan masalah besar, tapi menyenangkan untuk dimiliki. Seperti disebutkan di # 2, jika toko Anda memiliki penanganan pengiriman tindakan sinkron (dan seharusnya), Anda harus mengaktifkan tindakan ekstra untuk menangani hasil operasi asinkron. Melakukan pengiriman di pembuat tindakan berarti bahwa pembuat tindakan tunggal dapat mengirimkan ketiga jenis tindakan dengan menangani hasil akses data asinkron itu sendiri.

Michelle Tilley
sumber
15
Saya pikir apa yang berasal dari panggilan api web (pembuat tindakan vs. toko) kurang penting daripada fakta bahwa keberhasilan / kesalahan panggilan balik harus membuat tindakan. Jadi aliran data selalu: action -> dispatcher -> store -> views.
Fisherwebdev
1
Akankah menempatkan logika permintaan sebenarnya dalam modul API lebih baik / lebih mudah untuk diuji? Jadi modul API Anda bisa saja mengembalikan janji yang Anda kirim. Pembuat tindakan baru saja mengirimkan berdasarkan penyelesaian / kegagalan setelah mengirimkan tindakan 'tertunda' awal. Pertanyaan yang tersisa adalah bagaimana komponen mendengarkan 'peristiwa' ini karena saya tidak yakin bahwa status permintaan harus dipetakan ke status penyimpanan.
meja belakang
@ Backdesk Itulah yang saya lakukan pada contoh di atas: mengirimkan tindakan awal tertunda ( "SOME_ACTION"), menggunakan API untuk membuat permintaan ( SomeDataAccessLayer.doSomething(userId)) yang mengembalikan sebuah janji, dan dalam dua .thenfungsi, mengirimkan tindakan tambahan. Status permintaan dapat (lebih atau kurang) memetakan ke menyimpan status jika aplikasi perlu mengetahui tentang status negara. Bagaimana peta ini tergantung pada aplikasi (misalnya, mungkin setiap komentar memiliki status kesalahan tersendiri, ala Facebook, atau mungkin ada satu komponen kesalahan global)
Michelle Tilley
@MichelleTilley "salah satu konsep inti dalam fluks adalah untuk mencegah pengiriman bertingkat dan untuk mencegah beberapa pengiriman sekaligus; ini sangat sulit dilakukan saat toko Anda melakukan pemrosesan yang tidak sinkron." Itu poin penting bagi saya. Kata yang bagus.
51

Saya tweet pertanyaan ini ke devs di Facebook dan jawaban yang saya dapatkan dari Bill Fisher adalah:

Saat menanggapi interaksi pengguna dengan UI, saya akan membuat panggilan asinkron dalam metode pembuat tindakan.

Tetapi jika Anda memiliki ticker atau pengemudi non-manusia lainnya, panggilan dari toko akan bekerja lebih baik.

Yang penting adalah membuat tindakan di callback error / sukses sehingga data selalu berasal dari tindakan

Markus-ipse
sumber
Meskipun ini masuk akal, ada yang tahu mengapa a call from store works better when action triggers from non-human driver ?
SharpCoder
@SharpCoder Saya rasa jika Anda memiliki live-ticker atau sesuatu yang serupa, Anda tidak perlu melakukan tindakan dan ketika Anda melakukannya dari toko, Anda mungkin harus menulis lebih sedikit kode, karena toko dapat langsung mengakses status & berikan perubahan.
Florian Wendelborn
8

Toko harus melakukan segalanya, termasuk mengambil data, dan memberi sinyal ke komponen bahwa data toko telah diperbarui. Mengapa? Karena tindakan kemudian bisa ringan, sekali pakai, dan dapat diganti tanpa memengaruhi perilaku penting. Semua perilaku dan fungsi penting terjadi di toko. Ini juga mencegah duplikasi perilaku yang seharusnya disalin dalam dua tindakan yang sangat mirip tetapi berbeda. Toko-toko adalah satu - satunya sumber (menangani) kebenaran.

Dalam setiap implementasi Flux, saya telah melihat Actions pada dasarnya adalah event string yang diubah menjadi objek, seperti biasanya Anda akan memiliki event bernama "anchor: clicked" tetapi di Flux akan didefinisikan sebagai AnchorActions.Clicked. Mereka bahkan sangat "bodoh" sehingga sebagian besar implementasi memiliki objek Dispatcher terpisah untuk benar-benar mengirimkan peristiwa ke toko yang mendengarkan.

Secara pribadi saya suka implementasi Flux Reflux di mana tidak ada objek Dispatcher yang terpisah dan objek Action melakukan pengiriman sendiri.


sunting: Flux Facebook benar-benar menarik "pembuat tindakan" sehingga mereka menggunakan tindakan cerdas. Mereka juga menyiapkan muatan menggunakan toko:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreators.js#L27 (baris 27 dan 28)

Callback saat selesai kemudian akan memicu tindakan baru kali ini dengan data yang diambil sebagai payload:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

Jadi saya rasa itulah solusi yang lebih baik.

Rygu
sumber
Apa implementasi Reflux ini? Saya belum pernah mendengarnya. Jawaban Anda menarik. Maksud Anda, implementasi toko Anda harus memiliki logika untuk melakukan panggilan API dan seterusnya? Saya pikir toko seharusnya menerima data dan hanya memperbarui nilainya. Mereka memfilter tindakan tertentu, dan memperbarui beberapa atribut toko mereka.
Jeremy D
Reflux adalah variasi kecil dari Fluks Facebook: github.com/spoike/refluxjs Toko mengelola seluruh domain "Model" aplikasi Anda, vs Actions / Dispatchers yang hanya menjahit & merekatkan semuanya.
Rygu
1
Jadi saya telah memikirkan hal ini lagi dan (hampir) menjawab pertanyaan saya sendiri. Saya akan menambahkannya sebagai jawaban di sini (agar orang lain dapat memilih) tetapi tampaknya saya terlalu miskin karma di stackoverflow untuk dapat memposting jawaban. Jadi inilah tautannya: groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan
Terima kasih untuk tautan grup google, sepertinya sangat informatif. Saya juga lebih menyukai segala sesuatu yang melalui dispatcher, dan logika yang sangat sederhana di toko, pada dasarnya, memperbarui data mereka itu saja. @ Rygu Saya akan memeriksa refluks.
Jeremy D
Saya mengedit jawaban saya dengan tampilan alternatif. Sepertinya kedua solusi itu mungkin. Saya hampir pasti akan memilih solusi Facebook daripada yang lain.
Rygu
3

Saya akan memberikan argumen yang mendukung Tindakan "bodoh".

Dengan menempatkan tanggung jawab untuk mengumpulkan data tampilan di Tindakan Anda, Anda menghubungkan Tindakan Anda dengan persyaratan data tampilan Anda.

Sebaliknya, Actions generik, yang secara deklaratif mendeskripsikan maksud pengguna, atau beberapa transisi status dalam aplikasi Anda, memungkinkan Store apa pun yang merespons Action tersebut untuk mengubah maksud, menjadi status yang disesuaikan secara khusus untuk tampilan yang dilanggani.

Ini cocok untuk lebih banyak, tetapi lebih kecil, Toko yang lebih khusus. Saya memperdebatkan gaya ini karena

  • ini memberi Anda lebih banyak fleksibilitas dalam cara tampilan menggunakan data Store
  • Toko "pintar", yang dikhususkan untuk tampilan yang mengonsumsinya, akan lebih kecil dan kurang digabungkan untuk aplikasi yang kompleks, daripada Tindakan "cerdas", yang berpotensi menjadi tempat bergantung banyak tampilan

Tujuan Store adalah untuk menyediakan data ke tampilan. Nama "Tindakan" menunjukkan kepada saya bahwa tujuannya adalah untuk menjelaskan perubahan dalam Aplikasi saya.

Misalkan Anda harus menambahkan widget ke tampilan Dasbor yang ada, yang menunjukkan beberapa data agregat baru yang mewah yang baru saja diluncurkan oleh tim backend Anda.

Dengan Tindakan "cerdas", Anda mungkin perlu mengubah Tindakan "segarkan dasbor" Anda, untuk menggunakan API baru. Namun, "Menyegarkan dasbor" dalam arti abstrak tidak berubah. Persyaratan data dari pandangan Anda adalah apa yang telah berubah.

Dengan Tindakan "dumb", Anda dapat menambahkan Store baru untuk digunakan widget baru, dan mengaturnya sehingga ketika menerima jenis Tindakan "refresh-dashboard", ia mengirim permintaan untuk data baru, dan memaparkannya ke widget baru setelah siap. Masuk akal bagi saya bahwa saat lapisan tampilan membutuhkan lebih banyak atau berbagai data, hal-hal yang saya ubah adalah sumber datanya: Toko.

Carlos Lalimarmo
sumber
2

flux-react-router-demo gaeron memiliki variasi utilitas yang bagus dari pendekatan 'benar'.

ActionCreator membuat janji dari layanan API eksternal, lalu meneruskan janji dan tiga konstanta tindakan ke dispatchAsyncfungsi di proxy / Dispatcher yang diperluas. dispatchAsyncakan selalu mengirimkan tindakan pertama misalnya 'GET_EXTERNAL_DATA' dan setelah janji muncul, tindakan itu akan mengirimkan 'GET_EXTERNAL_DATA_SUCCESS' atau 'GET_EXTERNAL_DATA_ERROR'.

William Myers
sumber
1

Jika Anda ingin suatu hari memiliki lingkungan pengembangan yang sebanding dengan apa yang Anda lihat dalam video terkenal Bret Victor, Inventing on Principle , Anda sebaiknya menggunakan toko bodoh yang hanya merupakan proyeksi tindakan / peristiwa di dalam struktur data, tanpa efek samping. Ini juga akan membantu jika toko Anda sebenarnya adalah anggota dari struktur data global yang tidak berubah, seperti di Redux .

Penjelasan lebih lanjut di sini: https://stackoverflow.com/a/31388262/82609

Sebastien Lorber
sumber