Redux: Rekomendasi untuk praktik terbaik terkait pembuat aksi, reduksi, dan pemilih

Dibuat pada 22 Des 2015  ·  106Komentar  ·  Sumber: reduxjs/redux

Tim saya telah menggunakan Redux selama beberapa bulan sekarang. Sepanjang jalan saya kadang-kadang mendapati diri saya memikirkan sebuah fitur dan bertanya-tanya "apakah ini termasuk dalam pembuat aksi atau peredam?". Dokumentasi tampaknya agak kabur tentang fakta ini. (Atau mungkin saya baru saja melewatkan di mana itu tercakup, dalam hal ini saya minta maaf.) Tetapi karena saya telah menulis lebih banyak kode dan lebih banyak tes, saya menjadi memiliki pendapat yang lebih kuat tentang di mana hal-hal _seharusnya dan saya pikir itu akan bernilai berbagi dan berdiskusi dengan orang lain.

Jadi di sini adalah pikiran saya.

Gunakan pemilih di mana-mana

Yang pertama ini tidak sepenuhnya terkait dengan Redux tetapi saya akan tetap membagikannya karena disebutkan secara tidak langsung di bawah ini. Tim saya menggunakan rackt/reselect . Kami biasanya mendefinisikan file yang mengekspor penyeleksi untuk simpul tertentu dari pohon status kami (mis. MyPageSelectors). Wadah "pintar" kami kemudian menggunakan penyeleksi tersebut untuk membuat parameter komponen "bodoh" kami.

Seiring waktu kami menyadari bahwa ada manfaat tambahan menggunakan penyeleksi yang sama ini di tempat lain (tidak hanya dalam konteks pemilihan ulang). Misalnya, kami menggunakannya dalam pengujian otomatis. Kami juga menggunakannya dalam thunks yang dikembalikan oleh pembuat aksi (selengkapnya di bawah).

Jadi rekomendasi pertama saya adalah- gunakan penyeleksi bersama _everywhere_- bahkan ketika mengakses data secara sinkron (mis. prefer myValueSelector(state) daripada state.myValue ). Ini mengurangi kemungkinan variabel salah ketik yang mengarah ke nilai tidak terdefinisi yang halus, menyederhanakan perubahan pada struktur toko Anda, dll.

Lakukan _more_ di action-creator dan _less_ di reducer

Saya pikir yang satu ini sangat penting meskipun mungkin tidak langsung terlihat. Logika bisnis milik pencipta tindakan. Reducer harus bodoh dan sederhana. Dalam banyak kasus individu itu tidak masalah - tetapi konsistensi itu baik dan jadi yang terbaik adalah _secara konsisten_ melakukan ini. Ada beberapa alasan mengapa:

  1. Pembuat tindakan dapat asinkron melalui penggunaan middleware seperti redux-thunk . Karena aplikasi Anda akan sering memerlukan pembaruan asinkron ke toko Anda - beberapa "logika bisnis" akan berakhir dalam tindakan Anda.
  2. Pembuat tindakan (lebih tepatnya thunk yang mereka kembalikan) dapat menggunakan pemilih bersama karena mereka memiliki akses ke status lengkap. Reducer tidak bisa karena mereka hanya memiliki akses ke node mereka.
  3. Menggunakan redux-thunk , pembuat tindakan tunggal dapat mengirimkan beberapa tindakan- yang membuat pembaruan status yang rumit menjadi lebih sederhana dan mendorong penggunaan kembali kode yang lebih baik.

Bayangkan negara Anda memiliki metadata yang terkait dengan daftar item. Setiap kali item dimodifikasi, ditambahkan, atau dihapus dari daftar- metadata perlu diperbarui. "Logika bisnis" untuk menjaga agar daftar dan metadatanya tetap sinkron dapat diterapkan di beberapa tempat:

  1. Dalam reduksi. Setiap peredam (tambah, edit, hapus) bertanggung jawab untuk memperbarui daftar _serta_ metadata.
  2. Dalam tampilan (wadah/komponen). Setiap tampilan yang meminta tindakan (tambah, edit, hapus) itu juga bertanggung jawab untuk menjalankan tindakan updateMetadata . Pendekatan ini mengerikan karena (semoga) alasan yang jelas.
  3. Dalam aksi-pencipta. Setiap pembuat tindakan (tambah, edit, hapus) mengembalikan thunk yang mengirimkan tindakan untuk memperbarui daftar dan kemudian tindakan lain untuk memperbarui metadata.

Mengingat pilihan di atas, opsi 3 lebih baik. Kedua opsi 1 dan 3 mendukung berbagi kode bersih tetapi hanya opsi 3 yang mendukung kasus di mana pembaruan daftar dan/atau metadata mungkin tidak sinkron. (Misalnya mungkin itu bergantung pada pekerja web.)

Tulis tes "bebek" yang berfokus pada Tindakan dan Selektor

Cara paling efisien untuk menguji tindakan, reduksi, dan pemilih adalah dengan mengikuti pendekatan "bebek" saat menulis tes. Ini berarti Anda harus menulis satu set pengujian yang mencakup serangkaian tindakan, reduksi, dan pemilih tertentu, bukan 3 set pengujian yang berfokus pada masing-masing individu. Ini lebih akurat mensimulasikan apa yang terjadi di aplikasi Anda yang sebenarnya dan memberikan hasil maksimal.

Memecahnya lebih jauh, saya menemukan bahwa menulis tes yang berfokus pada pembuat tindakan dan kemudian memverifikasi hasilnya menggunakan penyeleksi adalah berguna. (Jangan langsung menguji reduksi.) Yang penting adalah tindakan tertentu menghasilkan keadaan yang Anda harapkan. Memverifikasi hasil ini menggunakan penyeleksi (bersama) Anda adalah cara untuk mencakup ketiganya dalam satu lintasan.

discussion

Komentar yang paling membantu

@dtinth @denis-sokolov Saya juga setuju dengan Anda tentang itu. Btw ketika saya merujuk proyek redux-saga, saya mungkin tidak menjelaskan bahwa saya menentang gagasan membuat actionCreators tumbuh dan menjadi semakin kompleks dari waktu ke waktu.

Proyek Redux-saga juga merupakan upaya untuk melakukan apa yang Anda gambarkan @dtinth tetapi ada perbedaan halus dengan apa yang Anda berdua katakan. Sepertinya Anda ingin mengatakan jika Anda menulis setiap peristiwa mentah yang terjadi pada log tindakan maka Anda dapat dengan mudah menghitung keadaan apa pun dari reduksi log tindakan ini. Ini sepenuhnya benar, dan saya telah mengambil jalan itu untuk sementara waktu sampai aplikasi saya menjadi sangat sulit untuk dipertahankan karena log tindakan menjadi tidak eksplisit, dan reduksi terlalu rumit dari waktu ke waktu.

Mungkin Anda dapat melihat titik diskusi asli yang mengarah ke diskusi Redux saga ini: https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment -162822909

Usecase untuk dipecahkan

Bayangkan Anda memiliki aplikasi Todo, dengan acara TodoCreated yang jelas. Kemudian kami meminta Anda untuk membuat kode onboarding aplikasi. Setelah pengguna membuat todo, kita harus mengucapkan selamat kepadanya dengan popup.

Cara "tidak murni":

Inilah yang tampaknya disukai @bvaughn

function createTodo(todo) {
   return (dispatch, getState) => {
       dispatch({type: "TodoCreated",payload: todo});
       if ( getState().isOnboarding ) {
         dispatch({type: "ShowOnboardingTodoCreateCongratulation"});
       }
   }
}

Saya tidak suka pendekatan ini karena membuat pembuat tindakan sangat digabungkan dengan tata letak tampilan aplikasi. Ini mengasumsikan actionCreator harus mengetahui struktur pohon status UI untuk mengambil keputusannya.

Cara "menghitung semuanya dari peristiwa mentah":

Inilah yang tampaknya disukai @denis-sokolov

function onboardingTodoCreateCongratulationReducer(state = defaultState, action) {
  var isOnboarding = isOnboardingReducer(state.isOnboarding,action);
  switch (action) {
    case "TodoCreated": 
        return {isOnboarding: isOnboarding, isCongratulationDisplayed: isOnboarding}
    default: 
        return {isOnboarding: isOnboarding, isCongratulationDisplayed: false}
  }
}

Ya, Anda dapat membuat peredam yang tahu jika ucapan selamat harus ditampilkan. Tetapi kemudian Anda memiliki popup yang akan ditampilkan bahkan tanpa tindakan yang mengatakan bahwa popup telah ditampilkan. Dalam pengalaman saya sendiri melakukan itu (dan masih memiliki kode warisan yang melakukan itu) selalu lebih baik untuk membuatnya sangat eksplisit: JANGAN PERNAH menampilkan sembulan ucapan selamat jika tidak ada tindakan DISPLAY_CONGRATULATION yang dipecat. Eksplisit jauh lebih mudah dipertahankan daripada implisit.

Cara saga yang disederhanakan.

Redux-saga menggunakan generator dan mungkin terlihat sedikit rumit jika Anda tidak terbiasa, tetapi pada dasarnya dengan implementasi yang disederhanakan, Anda akan menulis sesuatu seperti:

function createTodo(todo) {
   return (dispatch, getState) => {
       dispatch({type: "TodoCreated",payload: todo});
   }
}

function onboardingSaga(state, action, actionCreators) {
  switch (action) {
    case "OnboardingStarted": 
        return {onboarding: true, ...state};
    case "OnboardingStarted": 
        return {onboarding: false, ...state};
    case "TodoCreated": 
        if ( state.onboarding ) dispatch({type: "ShowOnboardingTodoCreateCongratulation"});
        return state;
    default: 
        return state;
  }
}

Saga adalah aktor stateful yang menerima peristiwa dan dapat menghasilkan efek. Di sini diimplementasikan sebagai peredam tidak murni untuk memberi Anda gambaran tentang apa itu tetapi sebenarnya tidak ada dalam proyek redux-saga.

Sedikit memperumit aturan:

Jika Anda menjaga aturan awal, itu tidak terlalu eksplisit tentang semuanya.
Jika Anda melihat implementasi di atas, Anda akan melihat bahwa popup ucapan selamat terbuka setiap kali kita membuat todo selama orientasi. Kemungkinan besar, kami ingin itu terbuka hanya untuk todo pertama yang dibuat yang terjadi selama orientasi dan tidak semuanya. Selain itu, kami ingin mengizinkan pengguna untuk akhirnya mengulang orientasi dari awal.

Bisakah Anda melihat bagaimana kode akan menjadi berantakan di ketiga implementasi seiring waktu karena orientasi menjadi semakin rumit?

Cara redux-saga

Dengan redux-saga dan aturan orientasi di atas, Anda akan menulis sesuatu seperti

function* onboarding() {
  while ( true ) {
    take(ONBOARDING_STARTED)
    take(TODO_CREATED)
    put(SHOW_TODO_CREATION_CONGRATULATION)
    take(ONBOARDING_ENDED)
  }
}

Saya pikir ini memecahkan kasus penggunaan ini dengan cara yang jauh lebih sederhana daripada solusi di atas. Jika saya salah, tolong beri saya implementasi Anda yang lebih sederhana :)

Anda berbicara tentang kode tidak murni, dan dalam hal ini tidak ada ketidakmurnian dalam implementasi Redux-saga karena efek take/put sebenarnya adalah data. Ketika take() dipanggil, ia tidak dieksekusi, ia mengembalikan deskriptor efek untuk dieksekusi, dan pada titik tertentu juru bahasa masuk, jadi Anda tidak perlu tiruan untuk menguji saga. Jika Anda seorang dev fungsional yang melakukan Haskell, pikirkan Free / IO monads.


Dalam hal ini diperbolehkan untuk:

  • Hindari memperumit actionCreator dan membuatnya bergantung pada getState
  • Jadikan implisit lebih eksplisit
  • Hindari menggabungkan logika transversal (seperti orientasi di atas) ke domain bisnis inti Anda (membuat todo)

Itu juga dapat memberikan lapisan interpretasi, memungkinkan untuk menerjemahkan peristiwa mentah menjadi peristiwa yang lebih bermakna/tingkat tinggi (sedikit seperti yang dilakukan ELM dengan membungkus peristiwa saat mereka menggelembung).

Contoh:

  • "TIMELINE_SCROLLED_NEAR-BOTTOM" dapat mengarah ke "NEXT_PAGE_LOADED"
  • "REQUEST_FAILED" dapat mengarah ke "USER_DISCONNECTED" jika kode kesalahan 401.
  • "HASHTAG_FILTER_ADDED" dapat mengarah ke "CONTENT_RELOADED"

Jika Anda ingin mencapai tata letak aplikasi modular dengan bebek, itu dapat memungkinkan untuk menghindari menggabungkan bebek bersama-sama. Saga menjadi titik penghubung. Bebek hanya perlu mengetahui peristiwa mentah mereka, dan hikayat menafsirkan peristiwa mentah ini. Ini jauh lebih baik daripada memiliki duck1 yang mengirimkan langsung tindakan duck2 karena membuat proyek duck1 lebih mudah digunakan kembali dalam konteks lain. Namun orang dapat berargumen bahwa titik penghubung juga bisa ada di actionCreators dan inilah yang dilakukan kebanyakan orang saat ini.

Semua 106 komentar

Penasaran apakah Anda menggunakan Immutable.js atau lainnya. Dalam beberapa hal redux yang saya buat, saya tidak dapat membayangkan tidak menggunakan yang

Wow. Sungguh suatu kekhilafan bagi saya _not_ untuk menyebutkan itu. Ya! Kami menggunakan Tidak Berubah! Seperti yang Anda katakan, sulit membayangkan _not_ menggunakannya untuk sesuatu yang substansial.

@bvaughn Satu area yang saya perjuangkan adalah di mana menarik garis antara Immutable dan komponen. Melewati objek yang tidak dapat diubah ke dalam Komponen memungkinkan Anda menggunakan dekorator/mixin yang dirender murni dengan sangat mudah tetapi kemudian Anda berakhir dengan kode yang tidak dapat diubah di komponen Anda (yang saya tidak suka). Sejauh ini saya baru saja menyerah dan melakukan itu, tetapi saya curiga Anda menggunakan penyeleksi dalam metode render() alih-alih mengakses metode Immutable.js secara langsung?

Sejujurnya ini adalah sesuatu yang belum kami definisikan sebagai kebijakan keras. Seringkali kita menggunakan penyeleksi dalam wadah "pintar" kita ke nilai asli ekstra dari objek abadi kita dan kemudian meneruskan nilai asli ke komponen kita sebagai string, boolean, dll. berikan tipe Record sehingga komponen dapat memperlakukannya seperti objek asli (dengan getter).

Saya telah bergerak ke arah yang berlawanan, membuat pembuat aksi lebih sepele. Tapi saya benar-benar baru memulai dengan redux. Beberapa pertanyaan tentang pendekatan Anda:

1) Bagaimana Anda menguji pembuat tindakan Anda? Saya suka memindahkan logika sebanyak mungkin ke fungsi sinkron murni yang tidak bergantung pada layanan eksternal karena lebih mudah untuk diuji.
2) Apakah Anda menggunakan perjalanan waktu dengan reload panas? Salah satu hal yang rapi dengan dengan react redux devtools adalah bahwa ketika memuat ulang panas diatur, toko akan menjalankan kembali semua tindakan terhadap peredam baru. Jika saya memindahkan logika saya ke pembuat aksi, saya akan kehilangan itu.
3) Jika pembuat tindakan Anda mengirim beberapa kali untuk menghasilkan efek, apakah itu berarti status Anda sebentar dalam status tidak valid? (Saya sedang memikirkan beberapa pengiriman sinkron di sini, bukan pengiriman yang tidak sinkron di kemudian hari)

Gunakan pemilih di mana-mana

Ya, sepertinya mengatakan reduksi Anda adalah detail implementasi dari status Anda dan bahwa Anda mengekspos status Anda ke komponen Anda melalui API kueri.
Seperti antarmuka apa pun yang diizinkan untuk memisahkan dan membuatnya mudah untuk memfaktorkan ulang keadaan.

Gunakan ImmutableJS

IMO dengan sintaks JS baru, penggunaan ImmutableJS tidak begitu berguna lagi karena Anda dapat dengan mudah memodifikasi daftar dan objek dengan JS normal. Kecuali jika Anda memiliki daftar dan objek yang sangat besar dengan banyak properti dan Anda memerlukan pembagian struktural untuk alasan kinerja, ImmutableJS bukanlah persyaratan yang ketat.

Lakukan lebih banyak dalam aksiCreator

@bvaughn Anda harus benar-benar melihat proyek ini: https://github.com/yelouafi/redux-saga
Ketika saya mulai membahas tentang saga (awalnya konsep backend) ke @yelouafi itu untuk menyelesaikan masalah seperti ini. Dalam kasus saya, saya pertama kali mencoba menggunakan saga saat menghubungkan pengguna pada aplikasi yang ada.

1) Bagaimana Anda menguji pembuat tindakan Anda? Saya suka memindahkan logika sebanyak mungkin ke fungsi sinkron murni yang tidak bergantung pada layanan eksternal karena lebih mudah untuk diuji.

Saya mencoba menjelaskan ini di atas, tetapi pada dasarnya... Saya pikir paling masuk akal (bagi saya sejauh ini) untuk menguji pembuat aksi Anda dengan pendekatan seperti "bebek". Mulailah pengujian dengan mengirimkan hasil pembuat tindakan, lalu verifikasi status menggunakan pemilih. Dengan cara ini- dengan satu tes Anda dapat mencakup pembuat tindakan, peredamnya, dan semua pemilih terkait.

2) Apakah Anda menggunakan perjalanan waktu dengan reload panas? Salah satu hal yang rapi dengan dengan react redux devtools adalah bahwa ketika memuat ulang panas diatur, toko akan menjalankan kembali semua tindakan terhadap peredam baru. Jika saya memindahkan logika saya ke pembuat aksi, saya akan kehilangan itu.

Tidak, kami tidak menggunakan perjalanan waktu. Tetapi mengapa logika bisnis Anda berada di pembuat tindakan memiliki dampak di sini? Satu-satunya hal yang memperbarui status aplikasi Anda adalah reduksi Anda. Dan menjalankan kembali tindakan yang dibuat akan mencapai hasil yang sama.

3) Jika pembuat tindakan Anda mengirim beberapa kali untuk menghasilkan efek, apakah itu berarti status Anda sebentar dalam status tidak valid? (Saya sedang memikirkan beberapa pengiriman sinkron di sini, bukan pengiriman yang tidak sinkron di kemudian hari)

Keadaan tidak valid sementara adalah sesuatu yang tidak dapat Anda hindari dalam beberapa kasus. Selama ada konsistensi akhirnya maka biasanya tidak menjadi masalah. Dan lagi, status Anda bisa sementara tidak valid terlepas dari logika bisnis Anda berada di pembuat tindakan atau reduksi. Ini lebih berkaitan dengan efek samping dan spesifikasi toko Anda.

IMO dengan sintaks JS baru, penggunaan ImmutableJS tidak begitu berguna lagi karena Anda dapat dengan mudah memodifikasi daftar dan objek dengan JS normal. Kecuali jika Anda memiliki daftar dan objek yang sangat besar dengan banyak properti dan Anda memerlukan pembagian struktural untuk alasan kinerja, ImmutableJS bukanlah persyaratan yang ketat.

Alasan utama untuk menggunakan Immutable (di mata saya) bukanlah kinerja atau gula sintaksis untuk pembaruan. Alasan utamanya adalah ini mencegah Anda (atau orang lain) dari _secara tidak sengaja_ mengubah status masuk Anda di dalam peredam. Itu tidak boleh dan sayangnya mudah dilakukan dengan objek JS biasa.

@bvaughn Anda harus benar-benar melihat proyek ini: https://github.com/yelouafi/redux-saga
Ketika saya mulai membahas tentang saga (awalnya konsep backend) ke @yelouafi itu untuk menyelesaikan masalah seperti ini. Dalam kasus saya, saya pertama kali mencoba menggunakan saga saat menghubungkan pengguna pada aplikasi yang ada.

Saya sebenarnya telah memeriksa proyek itu sebelumnya :) Meskipun saya belum menggunakannya. Itu memang terlihat rapi.

Saya mencoba menjelaskan ini di atas, tetapi pada dasarnya... Saya pikir paling masuk akal (bagi saya sejauh ini) untuk menguji pembuat aksi Anda dengan pendekatan seperti "bebek". Mulailah pengujian dengan mengirimkan hasil pembuat tindakan, lalu verifikasi status menggunakan pemilih. Dengan cara ini- dengan satu tes Anda dapat mencakup pembuat tindakan, peredamnya, dan semua pemilih terkait.

Maaf, saya mendapatkan bagian itu. Apa yang saya ingin tahu adalah bagian dari tes yang berinteraksi dengan asinkronisitas. Saya mungkin menulis tes seperti ini:

var store = createStore();
store.dispatch(actions.startRequest());
store.dispatch(actions.requestResponseReceived({...});
strictEqual(isLoaded(store.getState());

Tapi seperti apa ujianmu? Sesuatu seperti ini?

var mock = mockFetch();
store.dispatch(actions.request());
mock.expect("/api/foo.bar").andRespond("{status: OK}");
strictEqual(isLoaded(store.getState());

Tidak, kami tidak menggunakan perjalanan waktu. Tetapi mengapa logika bisnis Anda berada di pembuat tindakan memiliki dampak di sini? Satu-satunya hal yang memperbarui status aplikasi Anda adalah reduksi Anda. Dan menjalankan kembali tindakan yang dibuat akan mencapai hasil yang sama.

Bagaimana jika kodenya diubah? Jika saya mengganti peredam, tindakan yang sama diputar ulang tetapi dengan peredam baru. Sedangkan jika saya mengubah pembuat tindakan, itu versi baru tidak diputar ulang. Jadi untuk mempertimbangkan dua skenario:

Dengan peredam:

1) Saya mencoba tindakan di aplikasi saya.
2) Ada bug di peredam saya, yang menghasilkan status yang salah.
3) Saya memperbaiki bug di peredam dan menyimpan
4) Perjalanan waktu memuat peredam baru dan menempatkan saya dalam keadaan yang seharusnya saya alami.

Sedangkan dengan pencipta aksi

1) Saya mencoba tindakan di aplikasi saya.
2) Ada bug di pembuat tindakan, sehingga tindakan yang dibuat salah
3) Saya memperbaiki bug di pembuat tindakan dan menyimpan
4) Saya masih dalam kondisi yang salah, yang mengharuskan saya untuk setidaknya mencoba tindakan itu lagi, dan mungkin menyegarkan jika itu membuat saya dalam keadaan benar-benar rusak.

Keadaan tidak valid sementara adalah sesuatu yang tidak dapat Anda hindari dalam beberapa kasus. Selama ada konsistensi akhirnya maka biasanya tidak menjadi masalah. Dan lagi, status Anda bisa sementara tidak valid terlepas dari logika bisnis Anda berada di pembuat tindakan atau reduksi. Ini lebih berkaitan dengan efek samping dan spesifikasi toko Anda.

Saya kira cara berpikir saya tentang redux menegaskan bahwa toko selalu dalam keadaan yang valid. Pereduksi selalu mengambil status yang valid dan menghasilkan status yang valid. Menurut Anda, kasus apa yang memaksa seseorang untuk mengizinkan beberapa keadaan yang tidak konsisten?

Maaf mengganggu, tapi apa yang Anda maksud dengan status tidak valid dan valid?
di sini? Data sedang dimuat atau tindakan sedang dijalankan tetapi belum selesai terlihat
seperti keadaan sementara yang valid bagi saya.

Apa yang Anda maksud dengan status sementara di Redux @bvaughn dan @sompylasar ? Apakah pengiriman selesai, atau melempar. Jika melempar maka keadaan tidak berubah.

Kecuali jika peredam Anda memiliki masalah kode, Redux hanya memiliki status yang konsisten dengan logika peredam. Entah bagaimana semua tindakan yang dikirim ditangani dalam suatu transaksi: apakah seluruh pohon diperbarui, atau statusnya tidak berubah sama sekali.

Jika seluruh pohon diperbarui tetapi tidak dengan cara yang tepat (seperti keadaan yang tidak dapat dirender oleh React), hanya saja Anda tidak melakukan pekerjaan Anda dengan benar :)

Di Redux keadaan saat ini adalah untuk mempertimbangkan bahwa pengiriman tunggal adalah batas transaksi.

Namun saya memahami kekhawatiran @winstonewert yang tampaknya ingin mengirimkan 2 tindakan secara sinkron dalam transaksi yang sama. Karena terkadang actionCreators mengirimkan beberapa tindakan dan berharap semua tindakan dijalankan dengan benar. Jika 2 tindakan dikirim dan kemudian yang kedua gagal, maka hanya tindakan pertama yang akan diterapkan, yang mengarah ke keadaan yang dapat kita anggap "tidak konsisten". Mungkin @winstonewert menginginkan bahwa jika pengiriman tindakan ke-2 gagal, maka kami mengembalikan 2 tindakan tersebut.

@winstonewert Saya telah menerapkan sesuatu seperti itu dalam kerangka internal kami di sini dan berfungsi dengan baik sampai sekarang: https://github.com/stample/atom-react/blob/master/src/atom/atom.js
Saya juga ingin menangani kesalahan rendering: jika status tidak berhasil dirender, saya ingin status saya dibatalkan untuk menghindari pemblokiran UI. Sayangnya hingga rilis berikutnya React melakukan pekerjaan yang sangat buruk ketika metode render menimbulkan kesalahan sehingga tidak terlalu berguna tetapi mungkin di masa depan.

Saya cukup yakin bahwa kami dapat mengizinkan toko untuk menerima beberapa pengiriman sinkronisasi dalam transaksi dengan middleware.

Namun saya tidak yakin apakah mungkin untuk mengembalikan status jika terjadi kesalahan rendering, karena umumnya toko redux telah "berkomitmen" ketika kami mencoba merender statusnya. Dalam kerangka kerja saya, ada kait "beforeTransactionCommit" yang saya gunakan untuk memicu rendering dan akhirnya mengembalikan kesalahan render apa pun.

@gaearon Saya ingin tahu apakah Anda berencana untuk mendukung fitur semacam ini dan apakah mungkin dengan API saat ini.

Tampaknya bagi saya bahwa redux-batched-subscribe tidak mengizinkan untuk melakukan transaksi nyata tetapi hanya mengurangi jumlah rendering. Apa yang saya lihat adalah bahwa toko "berkomitmen" setelah setiap pengiriman meskipun pendengar berlangganan hanya dipecat sekali di akhir

Mengapa kita membutuhkan dukungan transaksi yang lengkap? Saya rasa saya tidak mengerti kasus penggunaan.

@gaearon Saya belum begitu yakin tetapi akan senang mengetahui lebih banyak tentang usecase @winstonewert .

Idenya adalah bahwa Anda dapat melakukan dispatch([a1,a2]) dan jika a2 gagal, maka kami mengembalikan ke keadaan sebelum a1 dikirim.

Di masa lalu saya sering mengirimkan beberapa tindakan secara sinkron (misalnya pada satu pendengar onClick, atau dalam actionCreator) dan terutama mengimplementasikan transaksi sebagai cara untuk memanggil render hanya di akhir semua tindakan yang dikirim, tetapi ini telah diselesaikan dengan cara yang berbeda oleh proyek redux-batched-subscribe.

Dalam kasus penggunaan saya, tindakan yang saya gunakan untuk menjalankan transaksi sebagian besar untuk menghindari rendering yang tidak perlu, tetapi tindakan itu masuk akal secara independen sehingga meskipun pengiriman gagal untuk tindakan ke-2, tidak mengembalikan tindakan pertama masih akan memberi saya status yang konsisten ( tapi mungkin bukan yang direncanakan...). Saya tidak benar-benar tahu apakah seseorang dapat membuat usecase di mana rollback penuh akan berguna

Namun ketika rendering gagal, bukankah masuk akal untuk mencoba mengembalikan ke status terakhir di mana render tidak gagal alih-alih mencoba membuat kemajuan pada status yang tidak dapat dirender?

Apakah penambah peredam sederhana akan berfungsi? misalnya

const enhanceReducerWithTheAbilityToConsumeMultipleActions = (reducer =>
  (state, actions) => (typeof actions.reduce === 'function'
    ? actions.reduce(reducer, state)
    : reducer(state, actions)
  )
)

Dengan itu, Anda dapat mengirimkan array ke toko. Enhancer membongkar tindakan individu dan memasukkannya ke setiap peredam.

Ohh @gaearon saya tidak tahu itu. Saya tidak melihat ada 2 proyek berbeda yang mencoba menyelesaikan kasus penggunaan yang sangat mirip dengan cara yang berbeda:

Keduanya akan mengizinkan untuk menghindari rendering yang tidak perlu, tetapi yang pertama akan mengembalikan semua tindakan batch sementara yang ke-2 hanya tidak akan menerapkan tindakan yang gagal.

@gaearon Aduh, saya salah karena tidak melihat itu. :merah:


Pembuat Tindakan mewakili Kode Tidak Murni

Saya mungkin belum memiliki banyak pengalaman langsung dengan Redux seperti kebanyakan orang, tetapi pada pandangan pertama, saya harus tidak setuju dengan "lakukan lebih banyak dalam pembuat aksi dan kurangi dalam reduksi," saya memiliki beberapa diskusi serupa di dalam kami perusahaan.

Dalam Cara Peretas: Memikirkan Kembali Pengembangan Aplikasi Web di Facebook tempat pola Flux diperkenalkan, masalah utama yang menyebabkan Flux ditemukan adalah kode imperatif.

Dalam hal ini, Pencipta Tindakan yang melakukan I/O adalah kode imperatif itu.

Kami tidak menggunakan Redux di tempat kerja, tetapi di tempat saya bekerja, kami dulu memiliki tindakan berbutir halus (yang, tentu saja, semuanya masuk akal dengan sendirinya) dan memicunya dalam batch. Misalnya, saat Anda mengeklik sebuah pesan, tiga tindakan dipicu: OPEN_MESSAGE_VIEW , FETCH_MESSAGE , MARK_NOTIFICATION_AS_READ .

Kemudian ternyata tindakan "tingkat rendah" ini tidak lebih dari "perintah" atau "penyetel" atau "pesan" untuk menetapkan beberapa nilai di dalam toko. Sebaiknya kita kembali dan menggunakan MVC dan berakhir dengan kode yang lebih sederhana jika kita terus melakukannya seperti ini.

Dalam arti tertentu, Action Creators mewakili kode tidak murni, sedangkan Reducer (dan Selector) mewakili kode murni . Orang-orang Haskell telah mengetahui bahwa lebih baik memiliki lebih sedikit kode yang tidak murni dan lebih banyak kode yang murni .

Misalnya, di proyek sampingan saya (menggunakan Redux), saya menggunakan API pengenalan suara webkit. Itu memancarkan acara onresult saat Anda berbicara. Ada dua pilihan — di mana acara ini diproses?

  • Minta pembuat tindakan memproses acara terlebih dahulu, lalu mengirimkannya ke toko.
  • Kirim saja objek acara ke toko.

Saya menggunakan nomor dua: Kirim saja objek acara mentah ke toko.

Namun, tampaknya Redux dev-tools tidak suka ketika objek non-polos dikirim ke toko, jadi saya menambahkan beberapa logika kecil di pembuat tindakan untuk mengubah objek acara ini menjadi objek biasa. (Kode di pembuat tindakan sangat sepele sehingga tidak mungkin salah.)

Kemudian peredam dapat menggabungkan peristiwa yang sangat primitif ini dan menyusun transkrip dari apa yang diucapkan. Karena logika itu hidup di dalam kode murni, saya dapat dengan mudah men-tweaknya secara langsung (dengan memuat ulang peredam panas).

Saya ingin mendukung @dtinth. Tindakan harus mewakili peristiwa yang terjadi dari dunia nyata, bukan bagaimana kita ingin bereaksi terhadap peristiwa tersebut. Secara khusus, lihat CQRS: kami ingin mencatat sebanyak mungkin detail tentang peristiwa kehidupan nyata, dan kemungkinan reduksi akan ditingkatkan di masa depan dan memproses peristiwa lama dengan logika baru.

@dtinth @denis-sokolov Saya juga setuju dengan Anda tentang itu. Btw ketika saya merujuk proyek redux-saga, saya mungkin tidak menjelaskan bahwa saya menentang gagasan membuat actionCreators tumbuh dan menjadi semakin kompleks dari waktu ke waktu.

Proyek Redux-saga juga merupakan upaya untuk melakukan apa yang Anda gambarkan @dtinth tetapi ada perbedaan halus dengan apa yang Anda berdua katakan. Sepertinya Anda ingin mengatakan jika Anda menulis setiap peristiwa mentah yang terjadi pada log tindakan maka Anda dapat dengan mudah menghitung keadaan apa pun dari reduksi log tindakan ini. Ini sepenuhnya benar, dan saya telah mengambil jalan itu untuk sementara waktu sampai aplikasi saya menjadi sangat sulit untuk dipertahankan karena log tindakan menjadi tidak eksplisit, dan reduksi terlalu rumit dari waktu ke waktu.

Mungkin Anda dapat melihat titik diskusi asli yang mengarah ke diskusi Redux saga ini: https://github.com/paldepind/functional-frontend-architecture/issues/20#issuecomment -162822909

Usecase untuk dipecahkan

Bayangkan Anda memiliki aplikasi Todo, dengan acara TodoCreated yang jelas. Kemudian kami meminta Anda untuk membuat kode onboarding aplikasi. Setelah pengguna membuat todo, kita harus mengucapkan selamat kepadanya dengan popup.

Cara "tidak murni":

Inilah yang tampaknya disukai @bvaughn

function createTodo(todo) {
   return (dispatch, getState) => {
       dispatch({type: "TodoCreated",payload: todo});
       if ( getState().isOnboarding ) {
         dispatch({type: "ShowOnboardingTodoCreateCongratulation"});
       }
   }
}

Saya tidak suka pendekatan ini karena membuat pembuat tindakan sangat digabungkan dengan tata letak tampilan aplikasi. Ini mengasumsikan actionCreator harus mengetahui struktur pohon status UI untuk mengambil keputusannya.

Cara "menghitung semuanya dari peristiwa mentah":

Inilah yang tampaknya disukai @denis-sokolov

function onboardingTodoCreateCongratulationReducer(state = defaultState, action) {
  var isOnboarding = isOnboardingReducer(state.isOnboarding,action);
  switch (action) {
    case "TodoCreated": 
        return {isOnboarding: isOnboarding, isCongratulationDisplayed: isOnboarding}
    default: 
        return {isOnboarding: isOnboarding, isCongratulationDisplayed: false}
  }
}

Ya, Anda dapat membuat peredam yang tahu jika ucapan selamat harus ditampilkan. Tetapi kemudian Anda memiliki popup yang akan ditampilkan bahkan tanpa tindakan yang mengatakan bahwa popup telah ditampilkan. Dalam pengalaman saya sendiri melakukan itu (dan masih memiliki kode warisan yang melakukan itu) selalu lebih baik untuk membuatnya sangat eksplisit: JANGAN PERNAH menampilkan sembulan ucapan selamat jika tidak ada tindakan DISPLAY_CONGRATULATION yang dipecat. Eksplisit jauh lebih mudah dipertahankan daripada implisit.

Cara saga yang disederhanakan.

Redux-saga menggunakan generator dan mungkin terlihat sedikit rumit jika Anda tidak terbiasa, tetapi pada dasarnya dengan implementasi yang disederhanakan, Anda akan menulis sesuatu seperti:

function createTodo(todo) {
   return (dispatch, getState) => {
       dispatch({type: "TodoCreated",payload: todo});
   }
}

function onboardingSaga(state, action, actionCreators) {
  switch (action) {
    case "OnboardingStarted": 
        return {onboarding: true, ...state};
    case "OnboardingStarted": 
        return {onboarding: false, ...state};
    case "TodoCreated": 
        if ( state.onboarding ) dispatch({type: "ShowOnboardingTodoCreateCongratulation"});
        return state;
    default: 
        return state;
  }
}

Saga adalah aktor stateful yang menerima peristiwa dan dapat menghasilkan efek. Di sini diimplementasikan sebagai peredam tidak murni untuk memberi Anda gambaran tentang apa itu tetapi sebenarnya tidak ada dalam proyek redux-saga.

Sedikit memperumit aturan:

Jika Anda menjaga aturan awal, itu tidak terlalu eksplisit tentang semuanya.
Jika Anda melihat implementasi di atas, Anda akan melihat bahwa popup ucapan selamat terbuka setiap kali kita membuat todo selama orientasi. Kemungkinan besar, kami ingin itu terbuka hanya untuk todo pertama yang dibuat yang terjadi selama orientasi dan tidak semuanya. Selain itu, kami ingin mengizinkan pengguna untuk akhirnya mengulang orientasi dari awal.

Bisakah Anda melihat bagaimana kode akan menjadi berantakan di ketiga implementasi seiring waktu karena orientasi menjadi semakin rumit?

Cara redux-saga

Dengan redux-saga dan aturan orientasi di atas, Anda akan menulis sesuatu seperti

function* onboarding() {
  while ( true ) {
    take(ONBOARDING_STARTED)
    take(TODO_CREATED)
    put(SHOW_TODO_CREATION_CONGRATULATION)
    take(ONBOARDING_ENDED)
  }
}

Saya pikir ini memecahkan kasus penggunaan ini dengan cara yang jauh lebih sederhana daripada solusi di atas. Jika saya salah, tolong beri saya implementasi Anda yang lebih sederhana :)

Anda berbicara tentang kode tidak murni, dan dalam hal ini tidak ada ketidakmurnian dalam implementasi Redux-saga karena efek take/put sebenarnya adalah data. Ketika take() dipanggil, ia tidak dieksekusi, ia mengembalikan deskriptor efek untuk dieksekusi, dan pada titik tertentu juru bahasa masuk, jadi Anda tidak perlu tiruan untuk menguji saga. Jika Anda seorang dev fungsional yang melakukan Haskell, pikirkan Free / IO monads.


Dalam hal ini diperbolehkan untuk:

  • Hindari memperumit actionCreator dan membuatnya bergantung pada getState
  • Jadikan implisit lebih eksplisit
  • Hindari menggabungkan logika transversal (seperti orientasi di atas) ke domain bisnis inti Anda (membuat todo)

Itu juga dapat memberikan lapisan interpretasi, memungkinkan untuk menerjemahkan peristiwa mentah menjadi peristiwa yang lebih bermakna/tingkat tinggi (sedikit seperti yang dilakukan ELM dengan membungkus peristiwa saat mereka menggelembung).

Contoh:

  • "TIMELINE_SCROLLED_NEAR-BOTTOM" dapat mengarah ke "NEXT_PAGE_LOADED"
  • "REQUEST_FAILED" dapat mengarah ke "USER_DISCONNECTED" jika kode kesalahan 401.
  • "HASHTAG_FILTER_ADDED" dapat mengarah ke "CONTENT_RELOADED"

Jika Anda ingin mencapai tata letak aplikasi modular dengan bebek, itu dapat memungkinkan untuk menghindari menggabungkan bebek bersama-sama. Saga menjadi titik penghubung. Bebek hanya perlu mengetahui peristiwa mentah mereka, dan hikayat menafsirkan peristiwa mentah ini. Ini jauh lebih baik daripada memiliki duck1 yang mengirimkan langsung tindakan duck2 karena membuat proyek duck1 lebih mudah digunakan kembali dalam konteks lain. Namun orang dapat berargumen bahwa titik penghubung juga bisa ada di actionCreators dan inilah yang dilakukan kebanyakan orang saat ini.

@slorber Ini adalah contoh yang bagus! Terima kasih telah meluangkan waktu untuk menjelaskan keuntungan dan kerugian dari setiap pendekatan dengan jelas. (Saya bahkan berpikir itu harus ada di dokumen.)

Saya dulu mengeksplorasi ide serupa (yang saya beri nama "komponen pekerja"). Pada dasarnya, ini adalah komponen React yang tidak merender apa pun ( render: () => null ), tetapi mendengarkan peristiwa (misalnya dari toko) dan memicu efek samping lainnya. Komponen pekerja itu kemudian dimasukkan ke dalam komponen root aplikasi. Hanya cara lain yang gila untuk menangani efek samping yang kompleks. :stuck_out_tongue:

Banyak diskusi di sini ketika saya sedang tidur.

@winstonewert , Anda menyampaikan poin bagus tentang perjalanan waktu dan memutar ulang kode kereta. Saya pikir jenis bug/perubahan tertentu tidak akan berfungsi dengan perjalanan waktu, tetapi saya pikir secara keseluruhan Anda benar.

@dtinth , maaf, tapi saya tidak mengikuti sebagian besar komentar Anda. Beberapa bagian dari kode "bebek" pembuat tindakan/pereduksi Anda harus tidak murni, karena beberapa bagiannya harus mengambil data. Lebih dari itu, kamu kehilangan aku. Salah satu tujuan utama dari posting awal saya hanyalah salah satu pragmatisme.


@winstonewert berkata, "Saya kira cara berpikir saya tentang redux menegaskan bahwa toko selalu dalam keadaan valid."
@slorber bertanya, "Apa yang Anda maksud dengan keadaan sementara di Redux @bvaughn dan @sompylasar ? Apakah pengiriman selesai, atau melempar. Jika melempar maka keadaan tidak berubah."

Aku cukup yakin kita memikirkan hal yang berbeda. Ketika saya mengatakan "keadaan tidak valid sementara" saya mengacu pada kasus penggunaan seperti berikut ini. Misalnya, saya dan seorang rekan baru-baru ini merilis redux-search . Middleware pencarian ini mendengarkan perubahan pada koleksi hal-hal yang dapat dicari dan kemudian (kembali) mengindeksnya untuk pencarian. Jika pengguna menyediakan teks filter, redux-search mengembalikan daftar uid sumber daya yang cocok dengan teks pengguna. Jadi pertimbangkan hal berikut:

Bayangkan toko aplikasi Anda berisi beberapa objek yang dapat dicari: [{id: 1, name: "Alex"}, {id: 2, name: "Brian"}, {id: 3, name: "Charles"}] . Pengguna telah memasukkan teks filter "e" dan middleware pencarian berisi larik id 1 dan 3. Sekarang bayangkan pengguna 1 (Alex) dihapus - baik sebagai respons terhadap tindakan pengguna secara lokal atau penyegaran data jarak jauh yang tidak lagi berisi catatan pengguna itu. Ketika peredam Anda memperbarui koleksi pengguna, toko Anda akan sementara tidak valid- karena pencarian redux akan merujuk id yang tidak lagi ada dalam koleksi. Setelah middleware berjalan lagi, itu akan memperbaiki status tidak valid. Hal semacam ini dapat terjadi kapan saja satu simpul pohon Anda terkait dengan simpul lain.


@slorber berkata, "Saya tidak suka pendekatan ini karena membuat pembuat tindakan sangat digabungkan dengan tata letak tampilan aplikasi. Ini mengasumsikan actionCreator harus mengetahui struktur pohon status UI untuk mengambil keputusannya."

Saya tidak mengerti apa yang Anda maksud dengan pendekatan yang menggabungkan pembuat tindakan "ke tata letak tampilan aplikasi". Pohon status _drives_ (atau menginformasikan) UI. Itulah salah satu tujuan utama Flux. Dan pembuat dan reduksi tindakan Anda, menurut definisi, digabungkan dengan status itu (tetapi tidak dengan UI).

Untuk apa nilainya, contoh kode yang Anda tulis sebagai sesuatu yang saya sukai bukanlah jenis yang ada dalam pikiran saya. Mungkin saya melakukan pekerjaan yang buruk dalam menjelaskan diri saya sendiri. Saya pikir kesulitan dalam membahas hal seperti ini adalah bahwa hal itu biasanya tidak terwujud dalam contoh sederhana atau umum. (Misalnya standar aplikasi TODO MVC tidak cukup rumit untuk diskusi bernuansa seperti ini.)

Diedit untuk kejelasan pada poin terakhir.

Btw @slorber di sini adalah contoh dari apa yang ada dalam pikiran saya. Ini sedikit dibuat-buat.

Katakanlah negara Anda memiliki banyak node. Salah satu node tersebut menyimpan sumber daya bersama. (Yang saya maksud dengan "bersama" adalah sumber daya yang di-cache secara lokal dan diakses oleh beberapa halaman dalam aplikasi Anda.) Sumber daya bersama ini memiliki pembuat tindakan dan reduksi ("bebek") sendiri. Node lain menyimpan informasi untuk halaman aplikasi tertentu. Halaman Anda juga memiliki bebeknya sendiri.

Katakanlah halaman Anda perlu memuat Hal terbaru dan terhebat dan kemudian mengizinkan pengguna untuk mengeditnya. Berikut adalah contoh pendekatan pembuat tindakan yang mungkin saya gunakan untuk situasi seperti itu:

import { fetchThing, thingSelector } from 'resources/thing/duck'
import { showError } from 'messages/duck'

export function fetchAndProcessThing ({ params }): Object {
  const { id } = params
  return async ({ dispatch, getState }) => {
    try {
      await dispatch(fetchThing({ id }))

      const thing = thingSelector(getState())

      dispatch({ type: 'PROCESS_THING', thing })
    } catch (err) {
      dispatch(showError(`Invalid thing id="${id}".`))
    }
  }
}

Mungkin @winstonewert menginginkan bahwa jika pengiriman tindakan ke-2 gagal, maka kami mengembalikan 2 tindakan tersebut.

Tidak. Saya tidak akan menulis pembuat tindakan yang mengirimkan dua tindakan. Saya akan mendefinisikan satu tindakan yang melakukan dua hal. OP tampaknya lebih suka pembuat tindakan yang mengirimkan tindakan yang lebih kecil yang memungkinkan status tidak valid sementara yang saya tidak suka.

Ketika peredam Anda memperbarui koleksi pengguna, toko Anda akan sementara tidak valid- karena pencarian redux akan merujuk id yang tidak lagi ada dalam koleksi. Setelah middleware berjalan lagi, itu akan memperbaiki status tidak valid. Hal semacam ini dapat terjadi kapan saja satu simpul pohon Anda terkait dengan simpul lain.

Ini sebenarnya jenis kasus yang mengganggu saya. Menurut saya, indeks idealnya adalah sesuatu yang ditangani sepenuhnya oleh peredam atau pemilih. Harus mengirimkan tindakan ekstra untuk menjaga pencarian tetap mutakhir tampaknya penggunaan redux yang kurang murni.

OP tampaknya lebih suka pembuat tindakan yang mengirimkan tindakan yang lebih kecil yang memungkinkan status tidak valid sementara yang saya tidak suka.

Tidak tepat. Saya lebih suka tindakan tunggal sehubungan dengan simpul pembuat tindakan Anda dari pohon negara. Tetapi jika satu "tindakan" pengguna konseptual memengaruhi beberapa node dari pohon keadaan, maka Anda harus mengirimkan beberapa tindakan. Anda dapat memanggil setiap tindakan secara terpisah (yang menurut saya _bad_) atau Anda dapat meminta pembuat tindakan tunggal mengirimkan tindakan (cara redux-thunk, yang menurut saya _lebih baik_ karena menyembunyikan informasi itu dari lapisan tampilan Anda).

Ini sebenarnya jenis kasus yang mengganggu saya. Menurut saya, indeks idealnya adalah sesuatu yang ditangani sepenuhnya oleh peredam atau pemilih. Harus mengirimkan tindakan ekstra untuk menjaga pencarian tetap mutakhir tampaknya penggunaan redux yang kurang murni.

Anda tidak mengirimkan tindakan ekstra. Pencarian adalah middleware. Ini otomatis. Tetapi memang ada keadaan sementara ketika dua simpul pohon Anda tidak setuju.

@bvaughn Oh, maaf karena terlalu murni!

Nah, kode tidak murni berkaitan dengan pengambilan data dan efek samping/IO lainnya, sedangkan kode murni tidak dapat memicu efek samping apa pun. Lihat tabel ini untuk perbandingan antara kode murni dan tidak murni.

Praktik terbaik Flux mengatakan bahwa suatu tindakan harus “menjelaskan tindakan pengguna, bukan penyetel.” Dokumen Flux juga mengisyaratkan lebih jauh dari mana tindakan ini seharusnya berasal:

Ketika data baru memasuki sistem, baik melalui seseorang yang berinteraksi dengan aplikasi atau melalui panggilan api web , data tersebut dikemas menjadi suatu tindakan — literal objek yang berisi bidang data baru dan jenis tindakan tertentu.

Pada dasarnya, tindakan adalah fakta/data yang menggambarkan “apa yang terjadi”, bukan apa yang seharusnya terjadi. Toko hanya dapat bereaksi terhadap tindakan ini secara serempak, dapat diprediksi, dan tanpa efek samping lainnya. Semua efek samping lainnya harus ditangani dalam pembuat aksi (atau saga :wink :).

Contoh

Saya tidak mengatakan ini adalah cara terbaik atau lebih baik daripada cara lain, atau bahkan cara yang baik. Tapi inilah yang saat ini saya anggap sebagai praktik terbaik.

Sebagai contoh, katakanlah pengguna ingin melihat papan skor yang memerlukan koneksi ke server jauh. Inilah yang seharusnya terjadi:

  • Pengguna mengklik tombol lihat papan skor.
  • Tampilan papan skor ditampilkan dengan indikator pemuatan.
  • Permintaan dikirim ke server untuk mengambil papan skor.
  • Tunggu tanggapannya.
  • Jika berhasil, tampilkan papan skor.
  • Jika gagal, papan skor akan tertutup dan kotak pesan muncul dengan pesan kesalahan. Pengguna dapat menutupnya.
  • Pengguna dapat menutup papan skor.

Dengan asumsi tindakan hanya dapat mencapai toko sebagai hasil dari tindakan pengguna atau respons server, kami dapat membuat 5 tindakan.

  • SCOREBOARD_VIEW (sebagai akibat dari pengguna mengklik tombol lihat papan skor)
  • SCOREBOARD_FETCH_SUCCESS (sebagai hasil dari respon sukses dari server)
  • SCOREBOARD_FETCH_FAILURE (akibat respon error dari server)
  • SCOREBOARD_CLOSE (sebagai akibat dari pengguna mengklik tombol tutup)
  • MESSAGE_BOX_CLOSE (sebagai akibat dari pengguna mengklik tombol tutup pada kotak pesan)

5 tindakan ini cukup untuk menangani semua persyaratan di atas. Anda dapat melihat bahwa 4 tindakan pertama tidak ada hubungannya dengan "bebek" apa pun. Setiap tindakan hanya menggambarkan apa yang terjadi di dunia luar (pengguna ingin melakukan ini, server mengatakan itu) dan dapat dikonsumsi oleh peredam apa pun. Kami juga tidak memiliki tindakan MESSAGE_BOX_OPEN , karena itu bukan "yang terjadi" (walaupun itulah yang seharusnya terjadi).

Satu-satunya cara untuk mengubah pohon keadaan adalah dengan memancarkan tindakan, objek yang menggambarkan apa yang terjadi.README-nya Redux

Mereka direkatkan dengan pembuat aksi ini:

function viewScoreboard () {
  return async function (dispatch) {
    dispatch({ type: 'SCOREBOARD_VIEW' })
    try {
      const result = fetchScoreboardFromServer()
      dispatch({ type: 'SCOREBOARD_FETCH_SUCCESS', result })
    } catch (e) {
      dispatch({ type: 'SCOREBOARD_FETCH_FAILURE', error: String(e) })
    }
  }
}
function closeScoreboard () {
  return { type: 'SCOREBOARD_CLOSE' }
}

Kemudian setiap bagian toko (diatur oleh reduksi) kemudian dapat bereaksi terhadap tindakan ini:

| Bagian dari Toko/Reducer | Perilaku |
| --- | --- |
| papan skorLihat | Perbarui visibilitas menjadi true pada SCOREBOARD_VIEW , false pada SCOREBOARD_CLOSE dan SCOREBOARD_FETCH_FAILURE |
| papan skorLoadingIndicator | Perbarui visibilitas menjadi true pada SCOREBOARD_VIEW , false pada SCOREBOARD_FETCH_* |
| papan skorData | Perbarui data di dalam toko di SCOREBOARD_FETCH_SUCCESS |
| kotak pesan | Perbarui visibilitas ke true dan simpan pesan di SCOREBOARD_FETCH_FAILURE , dan perbarui visibilitas ke false di MESSAGE_BOX_CLOSE |

Seperti yang Anda lihat, satu tindakan dapat memengaruhi banyak bagian toko. Toko hanya diberikan deskripsi tingkat tinggi dari suatu tindakan (apa yang terjadi?) daripada perintah (apa yang harus dilakukan?). Hasil dari:

  1. Lebih mudah untuk menentukan kesalahan.

Tidak ada yang dapat memengaruhi status kotak pesan. Tidak ada yang bisa menyuruhnya membuka dengan alasan apa pun. Itu hanya bereaksi terhadap langganannya (tindakan pengguna dan respons server).

Misalnya, jika server gagal mengambil papan skor, dan kotak pesan tidak muncul, Anda tidak perlu mencari tahu mengapa tindakan SHOW_MESSAGE_BOX tidak dikirim. Menjadi jelas bahwa kotak pesan tidak menangani tindakan SCOREBOARD_FETCH_FAILURE dengan benar.

Perbaikannya sepele dan dapat dimuat ulang dan perjalanan waktu.

  1. Pembuat aksi dan reduksi dapat diuji secara terpisah.

Anda dapat menguji apakah pembuat tindakan menggambarkan apa yang terjadi di dunia luar dengan benar, tanpa memperhatikan bagaimana toko bereaksi terhadap mereka.

Dengan cara yang sama, reduksi dapat dengan mudah diuji apakah ia bereaksi dengan benar terhadap tindakan dari dunia luar.

(Tes integrasi masih akan sangat berguna.)

Jangan khawatir. :) Saya menghargai klarifikasi lebih lanjut. Ini benar-benar terdengar seperti kita setuju di sini. Melihat contoh pembuat tindakan Anda, viewScoreboard , itu sangat mirip dengan contoh pembuat tindakan saya fetchAndProcessThing , tepat di atasnya.

Pembuat aksi dan reduksi dapat diuji secara terpisah.

Meskipun saya setuju dengan ini, saya pikir sering kali lebih pragmatis untuk mengujinya bersama-sama. Kemungkinan tindakan Anda _atau_ atau peredam Anda (mungkin keduanya) sangat sederhana sehingga nilai pengembalian atas upaya pengujian yang sederhana dalam isolasi agak rendah. Itu sebabnya saya mengusulkan untuk menguji pembuat aksi, peredam, dan penyeleksi terkait bersama-sama (sebagai "bebek").

Tetapi jika satu "tindakan" pengguna konseptual memengaruhi beberapa node dari pohon keadaan, maka Anda harus mengirimkan beberapa tindakan.

Di situlah saya pikir apa yang Anda lakukan berbeda dari apa yang dianggap sebagai praktik terbaik untuk redux. Saya pikir cara standarnya adalah memiliki satu tindakan yang ditanggapi oleh banyak simpul dari pohon negara.

Ah, pengamatan yang menarik @winstonewert. Kami telah mengikuti pola penggunaan konstanta tipe unik untuk setiap bundel "bebek" dan dengan demikian, respons peredam saja terhadap tindakan yang dikirim oleh pembuat tindakan saudaranya. Sejujurnya saya tidak yakin bagaimana perasaan saya, pada awalnya, tentang reduksi sewenang-wenang yang menanggapi suatu tindakan. Rasanya seperti enkapsulasi yang buruk.

Kami telah mengikuti pola penggunaan konstanta tipe unik untuk setiap bundel "bebek"

Perhatikan bahwa kami tidak mendukungnya di mana pun di dokumen ;-) Tidak mengatakan itu buruk, tetapi memberi orang ide-ide tertentu yang terkadang salah tentang Redux.

jadi dengan ekstensi, peredam hanya menanggapi tindakan yang dikirim oleh pembuat tindakan saudaranya

Tidak ada yang namanya pasangan peredam / pembuat tindakan di Redux. Itu murni hal Bebek. Beberapa orang menyukainya tetapi mengaburkan kekuatan dasar model Redux/Flux: mutasi status dipisahkan satu sama lain dan dari kode yang menyebabkannya.

Sejujurnya saya tidak yakin bagaimana perasaan saya, pada awalnya, tentang reduksi sewenang-wenang yang menanggapi suatu tindakan. Rasanya seperti enkapsulasi yang buruk.

Tergantung pada apa yang Anda anggap batas enkapsulasi. Tindakan bersifat global dalam aplikasi, dan saya pikir itu baik-baik saja. Satu bagian dari aplikasi mungkin ingin bereaksi terhadap tindakan bagian lain karena persyaratan produk yang kompleks, dan kami pikir ini baik-baik saja. Koplingnya minimal: yang Anda andalkan hanyalah string dan bentuk objek aksi. Manfaatnya adalah mudah untuk memperkenalkan turunan baru dari tindakan di berbagai bagian aplikasi tanpa membuat banyak kabel dengan pembuat tindakan. Komponen Anda tetap tidak mengetahui apa yang sebenarnya terjadi ketika suatu tindakan dikirim—ini diputuskan di ujung peredam.

Jadi rekomendasi resmi kami adalah Anda harus terlebih dahulu mencoba agar reduksi yang berbeda merespons tindakan yang sama. Jika canggung, maka tentu saja, buat pembuat tindakan terpisah. Tapi jangan mulai dengan pendekatan ini.

Kami merekomendasikan penggunaan penyeleksi—sebenarnya, kami menyarankan mengekspor fungsi penyimpanan yang dibaca dari status ("pemilih") bersama reduksi, dan selalu menggunakannya dalam mapStateToProps alih-alih hardcoding struktur status dalam komponen. Dengan cara ini mudah untuk mengubah bentuk keadaan internal. Anda dapat (tetapi tidak harus) menggunakan pemilihan ulang untuk kinerja, tetapi Anda juga dapat menerapkan penyeleksi secara naif seperti pada contoh shopping-cart .

Mungkin, itu bermuara pada apakah Anda memprogram dalam gaya imperatif atau gaya reaktif. Menggunakan bebek dapat menyebabkan tindakan dan reduksi menjadi sangat berpasangan, yang mendorong tindakan yang lebih penting.

  • Dalam gaya imperatif, toko diberi “apa yang harus dilakukan”, misalnya SHOW_MESSAGE_BOX atau SHOW_ERROR
  • Dalam gaya reaktif, toko diberi “fakta tentang apa yang terjadi”, misalnya DATA_FETCHING_FAILED atau USER_ENTERED_INVALID_THING_ID . Toko bereaksi sesuai.

Pada contoh sebelumnya, saya tidak memiliki pembuat tindakan SHOW_MESSAGE_BOX atau showError('Invalid thing id="'+id+'"') tindakan, karena itu bukan fakta. Itu perintah.

Setelah fakta itu masuk ke toko, Anda dapat menerjemahkan fakta itu ke dalam perintah, di dalam reduksi murni Anda, mis

// type Command = State → State
// :: Action → Command
function interpretAction (action) {
  switch (action.type) {
  case 'DATA_FETCHING_FAILED':
    return showErrorMessage('Data fetching failed')
    break
  case 'USER_ENTERED_INVALID_THING_ID':
    return showErrorMessage('User entered invalid thing ID')
    break
  case 'CLOSE_ERROR_MESSAGE':
    return hideErrorMessage()
    break
  default:
    return doNothing()
  }
}

// :: (State, Action) → State
function errorMessageReducer (state, action) {
  return interpretAction(action)(state)
}

const showErrorMessage = message => state => ({ visible: true, message })
const hideErrorMessage = () => state => ({ visible: false })
const doNothing = () => state => state

Ketika suatu tindakan masuk ke toko sebagai "fakta" daripada "perintah", ada sedikit kemungkinan itu bisa salah, karena, yah, itu fakta.

Sekarang, jika reduksi Anda salah mengartikan fakta itu, itu bisa diperbaiki dengan mudah dan perbaikannya bisa berjalan seiring waktu. Namun, jika pembuat tindakan Anda salah menafsirkan fakta itu, Anda perlu menjalankan kembali pembuat tindakan Anda.

Anda juga dapat mengubah peredam Anda sehingga ketika USER_ENTERED_INVALID_THING_ID diaktifkan, bidang teks ID hal diatur ulang. Dan perubahan itu juga berjalan melalui waktu. Anda juga dapat melokalkan pesan kesalahan Anda dan tanpa menyegarkan halaman. Itu mengencangkan loop umpan balik, dan membuat debugging dan tweaking jauh lebih mudah.

(Saya hanya berbicara tentang pro di sini, tentu ada kontra. Anda harus lebih memikirkan bagaimana merepresentasikan fakta itu, mengingat toko Anda hanya dapat menanggapi fakta ini secara serempak dan tanpa efek samping. Lihat diskusi tentang alternatif async/model efek samping dan pertanyaan ini saya posting di StackOverflow . Saya kira kita belum memahami bagian itu.)


Sejujurnya saya tidak yakin bagaimana perasaan saya, pada awalnya, tentang reduksi sewenang-wenang yang menanggapi suatu tindakan. Rasanya seperti enkapsulasi yang buruk.

Ini juga sangat umum untuk beberapa komponen untuk mengambil data dari toko yang sama. Ini juga cukup umum untuk satu komponen bergantung pada data dari beberapa bagian toko. Bukankah itu terdengar seperti enkapsulasi yang buruk? Untuk menjadi benar-benar modular, bukankah komponen React juga harus berada di dalam bundel "bebek"? ( Arsitektur Elm melakukan itu .)

React membuat UI Anda reaktif (karena itu namanya) dengan memperlakukan data dari toko Anda sebagai fakta. Jadi Anda tidak perlu memberi tahu pandangan Anda 'cara memperbarui UI.'

Dengan cara yang sama, saya juga percaya Redux/Flux membuat model data Anda reaktif , dengan memperlakukan tindakan sebagai fakta, jadi Anda tidak perlu memberi tahu model data Anda cara memperbarui diri.

Terima kasih telah meluangkan waktu untuk menulis dan membagikan pemikiran Anda, @dtinth. Juga terima kasih @gaearon untuk mempertimbangkan diskusi ini. (Saya tahu Anda memiliki banyak hal yang terjadi.) Anda berdua memberi saya beberapa hal tambahan untuk dipertimbangkan. :)

Ini juga sangat umum untuk beberapa komponen untuk mengambil data dari toko yang sama. Ini juga cukup umum untuk satu komponen bergantung pada data dari beberapa bagian toko. Bukankah itu terdengar seperti enkapsulasi yang buruk?

Eh... beberapa di antaranya subjektif, tapi tidak. Saya menganggap pembuat dan penyeleksi tindakan yang diekspor sebagai API untuk modul.

Bagaimanapun, saya pikir ini adalah diskusi yang bagus. Seperti yang disebutkan Thai dalam tanggapannya sebelumnya, ada pro dan kontra terhadap pendekatan yang sedang kita diskusikan ini. Sangat menyenangkan untuk mendapatkan wawasan tentang pendekatan orang lain. :)

By the way kotak pesan adalah contoh yang baik di mana saya lebih suka memiliki pembuat tindakan terpisah untuk ditampilkan. Sebagian besar karena saya ingin melewatkan waktu ketika itu dibuat sehingga dapat diberhentikan secara otomatis (dan pembuat tindakan adalah tempat Anda memanggil tidak murni Date.now() ), karena saya ingin mengatur pengatur waktu untuk mengabaikannya, saya ingin mendebounce timer itu, dll. Jadi saya akan mempertimbangkan kotak pesan kasus di mana "aliran tindakan"-nya cukup penting untuk menjamin tindakan pribadinya. Yang mengatakan mungkin apa yang saya jelaskan dapat diselesaikan dengan lebih elegan oleh https://github.com/yelouafi/redux-saga .

Saya menulis ini di obrolan Discord Reactiflux pada awalnya, tetapi diminta untuk menempelkannya di sini.

Saya telah banyak memikirkan hal yang sama baru-baru ini. Saya merasa pembaruan status dibagi menjadi tiga bagian.

  1. Pembuat tindakan diberikan jumlah minimum informasi yang diperlukan untuk menjalankan pembaruan. Yaitu apa pun yang dapat dihitung dari keadaan saat ini tidak boleh ada di dalamnya.
  2. Status ditanyai untuk informasi apa pun yang Anda perlukan untuk memenuhi pembaruan (misalnya ketika Anda ingin menyalin Todo dengan id X, Anda mengambil atribut Todo dengan id X sehingga Anda dapat membuat salinan). Ini dapat dilakukan di pembuat tindakan, dan info itu kemudian dimasukkan ke dalam objek tindakan. Ini menghasilkan objek aksi yang gemuk . ATAU bisa dihitung dalam peredam - benda aksi tipis .
  3. Berdasarkan informasi tersebut, logika peredam murni diterapkan untuk mendapatkan keadaan selanjutnya.

Sekarang, masalahnya adalah apa yang harus dimasukkan ke dalam pembuat aksi dan apa di peredam, pilihan antara objek aksi gemuk dan kurus. Jika Anda meletakkan semua logika di pembuat tindakan, Anda akan berakhir dengan objek tindakan gemuk yang pada dasarnya mendeklarasikan pembaruan ke status. Pengurang menjadi murni, bodoh, tambahkan-ini, hapus itu, perbarui fungsi-fungsi ini. Mereka akan mudah dibuat. Tetapi tidak banyak logika bisnis Anda yang akan ada di sana.

Jika Anda memasukkan lebih banyak logika ke dalam peredam, Anda berakhir dengan objek tindakan yang bagus dan tipis, sebagian besar logika data Anda di satu tempat, tetapi reduksi Anda lebih sulit untuk dibuat karena Anda mungkin memerlukan info dari cabang lain. Anda berakhir dengan reduksi besar atau reduksi yang mengambil argumen tambahan dari yang lebih tinggi di negara bagian.

Tidak tahu apa jawaban untuk masalah ini, tidak yakin apakah ada yang belum

Terima kasih telah berbagi pemikiran ini @tommikaikkonen. Saya sendiri masih ragu-ragu tentang apa "jawaban" itu di sini. Saya setuju dengan ringkasan Anda. Saya akan menambahkan satu catatan kecil ke bagian "letakkan semua logika di pembuat tindakan ...", yang memungkinkan Anda menggunakan penyeleksi (bersama) untuk membaca data yang dalam beberapa kasus bisa bagus.

Ini adalah utas yang menarik! Mencari tahu di mana menempatkan kode di aplikasi redux adalah masalah yang saya kira kita semua hadapi. Saya suka ide CQRS untuk merekam hal-hal yang telah terjadi.
Tapi saya bisa melihat ketidakcocokan ide di sini karena di CQRS, AFAIK praktik terbaik adalah membangun keadaan de-normalisasi dari tindakan/peristiwa yang langsung dikonsumsi oleh pandangan. Tetapi dalam redux, praktik terbaik adalah membangun status yang sepenuhnya dinormalisasi dan memperoleh data tampilan Anda melalui penyeleksi.
Jika kita membangun keadaan terdenormalisasi yang dapat dikonsumsi langsung oleh tampilan, maka saya pikir masalah bahwa peredam menginginkan data di peredam lain hilang (karena setiap peredam hanya dapat menyimpan semua data yang diperlukan tanpa mempedulikan normalisasi). Tapi kemudian kami mendapatkan masalah lain saat memperbarui data. Mungkin ini inti pembahasannya?

Berasal dari pengembangan Berorientasi Objek selama bertahun-tahun.. Redux terasa seperti langkah mundur yang besar. Saya menemukan diri saya memohon untuk membuat kelas yang merangkum peristiwa (pembuat tindakan), dan logika bisnis. Saya masih mencoba mencari kompromi yang berarti tetapi sampai sekarang belum dapat melakukannya. Apakah ada orang lain yang merasakan hal yang sama?

Pemrograman berorientasi objek mendorong menempatkan membaca bersama-sama dengan menulis. Ini membuat banyak hal bermasalah: snapshotting dan rollback, logging terpusat, debug mutasi status yang salah, pembaruan efisien granular. Jika Anda tidak merasa bahwa itu adalah masalah bagi Anda, jika Anda tahu cara menghindarinya saat menulis kode MVC berorientasi objek tradisional, dan jika Redux menimbulkan lebih banyak masalah daripada yang dipecahkan di aplikasi Anda, jangan gunakan Redux :wink: .

@jayesbe Berasal dari latar belakang pemrograman berorientasi objek, Anda mungkin menemukan bahwa itu berbenturan dengan ide-ide yang muncul dalam pemrograman. Ini adalah salah satu dari banyak artikel tentang masalah ini: https://www.leaseweb.com/labs/2015/08/object-oriented-programming-is-exceptionally-bad/.

Dengan memisahkan tindakan dari transformasi data, pengujian penerapan aturan bisnis menjadi lebih sederhana. Transformasi menjadi kurang tergantung pada konteks aplikasi.

Itu tidak berarti meninggalkan objek atau kelas dalam Javascript. Misalnya, komponen React diimplementasikan sebagai objek, dan sekarang kelas. Tetapi komponen React dirancang hanya untuk membuat proyeksi data yang disediakan. Anda dianjurkan untuk menggunakan komponen murni yang tidak menyimpan status.

Di situlah Redux masuk: untuk mengatur status aplikasi dan menyatukan tindakan dan transformasi status aplikasi yang sesuai.

@johnsoftek terima kasih atas tautannya. Namun berdasarkan pengalaman saya dalam 10 tahun terakhir.. Saya tidak setuju tetapi kita tidak perlu masuk ke perdebatan antara OO dan non-OO di sini. Masalah yang saya miliki adalah dengan mengatur kode dan abstraksi.

Tujuan saya adalah membuat aplikasi tunggal/arsitektur tunggal yang dapat (menggunakan nilai konfigurasi saja) digunakan untuk membuat 100-an aplikasi. Kasus penggunaan yang harus saya tangani adalah menangani solusi perangkat lunak label putih yang digunakan oleh banyak klien.. masing-masing memanggil aplikasi mereka sendiri.

Saya telah menemukan apa yang saya rasa merupakan kompromi yang menarik .. dan saya pikir itu menanganinya dengan cukup baik tetapi mungkin tidak memenuhi standar kerumunan pemrograman fungsional. Saya masih ingin meletakkannya di sana.

Saya memiliki satu kelas Aplikasi yang mandiri dengan semua logika bisnis, pembungkus API, dll. yang saya perlukan untuk berinteraksi dengan aplikasi sisi server saya.

contoh..

export default Application {
    constructor(config) {
        this.config = config;
    } 

    config() {
        return this.config;
    }

    login(data, cb) {
        const url = [
            this.config.url,
            '?client=' + this.config.client,
            '&username=' + data.username,
            ....
        ].join('');

        fetch(url).then((responseText) => {
            cb(responseText);
        })
    }

    ... more business logic 
}

Saya membuat satu contoh objek ini dan menempatkannya ke dalam konteks .. dengan memperluas Penyedia Redux

import { Provider } from 'react-redux';

export default class MyProvider extends Provider {
    getChildContext() {
        return Object.assign({}, Provider.prototype.getChildContext.call(this), {
            app: this.props.app
        });
    }

    render() {
        return this.props.children;
    }
}
MyProvider.childContextTypes = {
    store: React.PropTypes.object,
    app: React.PropTypes.object
}

Lalu saya menggunakan penyedia ini seperti itu

import Application from './application';
import config from './config';

class MyApp extends Component {
  render() {
    return (
      <MyProvider store={store} app={new Application(config)}>
        <Router />
      </MyProvider>
    );
  }
}

AppRegistry.registerComponent('MyApp', () => MyApp);

akhirnya di Komponen saya, saya menggunakan aplikasi saya ..

class Login extends React.Component {
    render() {
        const { app } = this.context;
        const { state, actions } = this.props;
        return (
              <View style={style.transparentContainer}>
                <Form ref="form" type={User} options={options} />
                <Button 
                  onPress={() => {
                    value = this.refs.form.getValue();
                    if (value) {
                      app.login(value, actions.login);
                    }
                  }}
                >
                  Login
                </Button>
              </View>
        );
    }
};
Login.contextTypes = {
  app: React.PropTypes.object,
};

function mapStateToProps(state) {
  return {
      state: state.default.auth
  };
};

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(authActions, dispatch),
    dispatch
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Dengan demikian, Pencipta Tindakan hanyalah fungsi panggilan balik ke logika bisnis saya.

                      app.login(value, actions.login);

Solusi ini tampaknya berfungsi dengan baik saat ini meskipun saya baru memulai dengan otentikasi.

Saya yakin saya juga dapat meneruskan toko ke instance Aplikasi saya tetapi saya tidak ingin melakukan itu karena saya tidak ingin toko mengalami mutasi kebetulan. Meskipun mengakses toko mungkin berguna. Saya akan memikirkannya lagi jika perlu.

Saya telah menemukan apa yang saya rasa merupakan kompromi yang menarik .. dan saya pikir itu menanganinya dengan cukup baik tetapi mungkin tidak memenuhi standar kerumunan pemrograman fungsional.

Anda tidak akan menemukan "kerumunan fungsional" di sini :wink: . Alasan kami memilih solusi fungsional di Redux bukan karena kami dogmatis tetapi karena solusi tersebut memecahkan beberapa masalah yang sering dibuat orang karena kelas. Misalnya, memisahkan reduksi dari pembuat tindakan memungkinkan kita memisahkan pembacaan dan penulisan yang penting untuk mencatat dan mereproduksi bug. Tindakan menjadi objek biasa memungkinkan perekaman dan pemutaran ulang karena dapat diserialisasi. Demikian pula, status menjadi objek biasa daripada turunan dari MyAppState membuatnya sangat mudah untuk membuat serialisasi di server dan deserialize pada klien untuk rendering server, atau mempertahankan bagiannya di localStorage . Mengekspresikan reduksi sebagai fungsi memungkinkan kita untuk mengimplementasikan perjalanan waktu dan hot reload, dan mengekspresikan pemilih sebagai fungsi membuat memoisasi mudah ditambahkan. Semua manfaat ini tidak ada hubungannya dengan kita menjadi "kerumunan fungsional" dan segala sesuatu yang berkaitan dengan menyelesaikan tugas-tugas tertentu perpustakaan ini diciptakan untuk dipecahkan.

Saya membuat satu contoh objek ini dan menempatkannya ke dalam konteks .. dengan memperluas Penyedia Redux

Ini terlihat sangat masuk akal bagi saya. Kami tidak memiliki kebencian irasional terhadap kelas. Intinya adalah bahwa kami lebih suka tidak menggunakannya dalam kasus di mana mereka sangat membatasi (seperti untuk reduksi atau objek tindakan), tetapi tidak apa-apa untuk menggunakannya untuk menghasilkan objek tindakan, misalnya.

Namun saya akan menghindari memperpanjang Provider karena ini rapuh. Tidak perlu untuk itu: React menggabungkan konteks komponen, jadi Anda bisa membungkusnya saja.

import { Component } from 'react';
import { Provider } from 'react-redux';

export default class MyProvider extends Component {
    getChildContext() {
        return {
            app: this.props.app
        };
    }

    render() {
        return (
            <Provider store={this.props.store}>
                {this.props.children}
            </Provider>
        );
    }
}
MyProvider.childContextTypes = {
    app: React.PropTypes.object
}
MyProvider.propTypes = {
    app: React.PropTypes.object,
    store: React.PropTypes.object
}

Ini sebenarnya lebih mudah dibaca dalam pandangan saya, dan kurang rapuh.

Jadi, secara keseluruhan, pendekatan Anda masuk akal. Menggunakan kelas dalam hal ini tidak terlalu berbeda dari sesuatu seperti createActions(config) yang merupakan pola yang juga kami sarankan jika Anda perlu memparametrikan pembuat tindakan. Sama sekali tidak ada yang salah dengan itu.

Kami hanya mencegah Anda menggunakan instance kelas untuk objek status dan tindakan karena instance kelas membuat serialisasi menjadi sangat rumit. Untuk reducer, kami juga tidak menyarankan menggunakan class karena akan lebih sulit menggunakan komposisi reducer, yaitu reducer yang memanggil reducer lain. Untuk yang lainnya, Anda dapat menggunakan cara pengorganisasian kode apa pun, termasuk kelas.

Jika aplikasi dan konfigurasi Anda tidak dapat diubah (dan saya pikir memang seharusnya demikian, tetapi mungkin saya sudah minum terlalu banyak bantuan dingin fungsional), maka Anda dapat mempertimbangkan pendekatan berikut:

const appSelector = createSelector(
   (state) => state.config,
   (config) => new Application(config)
)

Dan kemudian di mapStateToProps:

function mapStateToProps(state) {
  return {
      app: appSelector(state)
  };
};

Maka Anda tidak memerlukan teknik penyedia yang Anda adopsi, Anda cukup mendapatkan objek aplikasi dari status. Berkat pemilihan ulang, objek Aplikasi hanya akan dibangun ketika konfigurasi berubah, yang mungkin hanya sekali.

Di mana saya pikir pendekatan ini mungkin memiliki keuntungan adalah dengan mudah memungkinkan Anda memperluas ide untuk memiliki beberapa objek seperti itu dan juga membuat objek tersebut bergantung pada bagian lain dari negara bagian. Misalnya, Anda dapat memiliki kelas UserControl dengan metode login/logout/etc yang memiliki akses ke konfigurasi dan bagian dari status Anda.

Jadi, secara keseluruhan, pendekatan Anda masuk akal.

:+1: Terima kasih itu membantu. Saya setuju dengan peningkatan pada MyProvider. Saya akan memperbarui kode saya untuk mengikuti. Salah satu masalah terbesar yang saya miliki ketika pertama kali mempelajari Redux adalah gagasan semantik "Action Creators" .. tidak cocok sampai saya menyamakannya dengan Acara. Bagi saya itu semacam realisasi seperti .. ini adalah peristiwa yang sedang dikirim.

@winstonewert apakah createSelector tersedia di react-native ? Saya tidak percaya itu. Pada saat yang sama, sepertinya Anda membuat Aplikasi baru setiap kali Anda melampirkannya di mapStateToProps di beberapa komponen ? Tujuan saya adalah memiliki satu objek yang dipakai yang menyediakan semua logika bisnis ke aplikasi dan agar objek itu dapat diakses secara global. Saya tidak yakin apakah saran Anda berhasil. Meskipun saya menyukai gagasan memiliki objek tambahan yang tersedia jika diperlukan.. secara teknis saya dapat membuat instance seperlunya melalui instance Aplikasi juga.

Salah satu masalah terbesar yang saya miliki ketika pertama kali mempelajari Redux adalah gagasan semantik "Action Creators" .. tidak cocok sampai saya menyamakannya dengan Acara. Bagi saya itu semacam realisasi seperti .. ini adalah peristiwa yang sedang dikirim.

Saya akan mengatakan bahwa tidak ada gagasan semantik tentang Pencipta Tindakan di Redux sama sekali. Ada gagasan semantik tentang Tindakan (yang menggambarkan apa yang terjadi dan secara kasar setara dengan peristiwa tetapi tidak cukup—misalnya lihat diskusi di #351). Pembuat tindakan hanyalah pola untuk mengatur kode. Lebih mudah untuk memiliki pabrik untuk tindakan karena Anda ingin memastikan bahwa tindakan dari jenis yang sama memiliki struktur yang konsisten, memiliki efek samping yang sama sebelum dikirim, dll. Tetapi dari sudut pandang Redux, pembuat tindakan tidak ada—Redux hanya melihat tindakan.

apakah createSelector tersedia di react-native ?

Ini tersedia di Reselect yang merupakan JavaScript biasa tanpa ketergantungan dan dapat bekerja di web, di asli, di server, dll.

Ahh. OK mengerti. Hal-hal yang jauh lebih jelas. Bersulang.

Jangan membuat sarang objek di mapStateToProps dan mapDispatchToProps

Saya baru-baru ini mengalami masalah di mana objek bersarang hilang ketika Redux menggabungkan mapStateToProps dan mapDispatchToProps (lihat reactjs/react-redux#324). Meskipun @gaearon memberikan solusi yang memungkinkan saya untuk menggunakan objek bersarang, ia melanjutkan dengan mengatakan bahwa ini adalah anti-pola:

Perhatikan bahwa mengelompokkan objek seperti ini akan menyebabkan alokasi yang tidak perlu dan juga akan membuat pengoptimalan kinerja menjadi lebih sulit karena kita tidak dapat lagi mengandalkan persamaan dangkal dari props hasil sebagai cara untuk mengetahui apakah mereka berubah. Jadi Anda akan melihat lebih banyak render daripada dengan pendekatan sederhana tanpa namespace yang kami rekomendasikan di dokumen.

@bvaughn berkata

reduksi harus bodoh dan sederhana

dan kita harus menempatkan sebagian besar logika bisnis ke dalam pembuat tindakan, saya sangat setuju dengan itu. Tetapi jika semuanya telah bergerak, mengapa kita masih harus membuat file dan fungsi peredam secara manual? Mengapa tidak menyimpan data yang telah dioperasikan dalam tindakan ke dalam penyimpanan secara langsung?

Ini telah membingungkan saya selama beberapa waktu ...

mengapa kita masih harus membuat file dan fungsi peredam secara manual?

Karena reduksi adalah fungsi murni, jika ada kesalahan dalam logika pembaruan status, Anda dapat memuat ulang peredam panas. Alat dev kemudian dapat memundurkan aplikasi ke keadaan awalnya, dan kemudian memutar ulang semua tindakan, menggunakan peredam baru. Ini berarti Anda dapat memperbaiki bug yang memperbarui status tanpa harus melakukan roll-back secara manual dan melakukan kembali tindakan tersebut. Ini adalah manfaat dari menjaga sebagian besar logika pembaruan status di peredam.

Ini adalah manfaat dari menjaga sebagian besar logika pembaruan status di peredam.

@dtinth Hanya untuk memperjelas, dengan mengatakan "logika pembaruan keadaan" maksud Anda "logika bisnis"?

Saya tidak begitu yakin menempatkan sebagian besar logika Anda dalam tindakan pencipta adalah ide yang bagus. Jika semua reduksi Anda hanyalah fungsi sepele yang menerima ADD_X -seperti tindakan, maka mereka tidak mungkin memiliki kesalahan - bagus! Tapi kemudian semua kesalahan Anda telah didorong ke pembuat tindakan Anda dan Anda kehilangan pengalaman debug hebat yang disinggung oleh

Tetapi juga seperti yang disebutkan @tommikaikkonen , menulis reduksi kompleks tidak sesederhana itu. Perasaan saya adalah di situlah Anda ingin mendorong jika Anda ingin menuai manfaat dari Redux - jika tidak, alih-alih mendorong efek samping ke tepi, Anda mendorong fungsi murni Anda untuk menangani hanya tugas yang paling sepele, meninggalkan sebagian besar aplikasi Anda di neraka yang dikuasai negara. :)

@sompylasar "logika bisnis" dan "logika pembaruan negara", imo, adalah hal yang sama.

Namun untuk berpadu dengan spesifik implementasi saya sendiri .. Tindakan saya terutama mencari input ke dalam Tindakan. Sebenarnya semua Tindakan saya murni karena saya telah memindahkan semua "logika bisnis" saya ke dalam konteks aplikasi.

sebagai contoh .. ini peredam khas saya

export default function reducer(state = initialState, action = {}) {
  switch (action.type) {
    case 'FOO_REQUEST':
    case 'FOO_RESPONSE':
    case 'FOO_ERROR':
    case 'FOO_RESET':
      return {
        ...state,
        ...action.data
      }; 
    default:
      return state;
  }
}

Tindakan khas saya:

export function fooRequest( res ) {
  return {
    type: 'FOO_REQUEST',
    data: {
        isFooing: true,
        toFoo: res.saidToFoo
    }
  };
}

export function fooResponse( res ) {
  return {
    type: 'FOO_RESPONSE',
    data: {
        isFooing: false,
        isFooed: true,
        fooData: res.data
    }
  };
}

export function fooError( res ) {
  return {
    type: 'FOO_ERROR',
    data: {
        isFooing: false,
        fooData: null,
        isFooed: false,
        fooError: res.error
    }
  };
}

export function fooReset( res ) {
  return {
    type: 'FOO_RESET',
    data: {
        isFooing: false,
        fooData: null,
        isFooed: false,
        fooError: null,
        toFoo: true
    }
  };
}

Logika bisnis saya didefinisikan dalam objek yang disimpan dalam konteks, yaitu..

export default class FooBar
{
    constructor(store)
    {
        this.actions = bindActionCreators({
            ...fooActions
        }, store.dispatch);
    }

    async getFooData()
    {
        this.actions.fooRequest({
            saidToFoo: true
        });

        fetch(url)
        .then((response) => {
            this.actions.fooResponse(response);
        })
    }
}

Jika Anda melihat komentar saya di atas, saya juga berjuang dengan pendekatan terbaik .. Saya akhirnya melakukan refactored dan menyelesaikan dengan meneruskan toko ke konstruktor objek aplikasi saya dan menghubungkan semua tindakan ke operator di titik pusat ini. Semua tindakan yang diketahui aplikasi saya ditugaskan di sini.

Saya tidak lagi menggunakan mapDispatchToProps() di mana pun. Untuk Redux saya sekarang hanya mapStateToProps saat membuat komponen yang terhubung. Jika saya perlu memicu tindakan apa pun .. Saya dapat memicunya melalui objek aplikasi saya melalui konteks.

class SomeComponent extends React.Component {
    componentWillReceiveProps(nextProps) {
        if (nextProps.someFoo != this.props.someFoo) {
            const { app } = this.context;
            app.actions.getFooData();
        }
    }
}
SomeComponent.contextTypes = {
    app: React.PropTypes.object
};

Komponen di atas tidak perlu disambungkan ke redux. Itu masih bisa mengirimkan tindakan. Tentu saja jika Anda perlu memperbarui status di dalam komponen, Anda akan mengubahnya menjadi komponen yang terhubung untuk memastikan bahwa perubahan status disebarkan.

Inilah cara saya mengatur "logika bisnis" inti saya. Karena status saya benar-benar dipertahankan di server backend.. ini bekerja sangat baik untuk kasus penggunaan saya.

Di mana Anda menyimpan "logika bisnis" Anda benar-benar terserah Anda dan bagaimana hal itu sesuai dengan kasus penggunaan Anda.

@jayesbe Bagian berikut berarti Anda tidak memiliki "logika bisnis" di reduksi, dan, terlebih lagi, struktur status telah pindah ke pembuat tindakan yang membuat muatan yang ditransfer ke toko melalui peredam:

    case 'FOO_REQUEST':
    case 'FOO_RESPONSE':
    case 'FOO_ERROR':
    case 'FOO_RESET':
      return {
        ...state,
        ...action.data
      }; 
export function fooRequest( res ) {
  return {
    type: 'FOO_REQUEST',
    data: {
        isFooing: true,
        toFoo: res.saidToFoo
    }
  };
}

@jayesbe Tindakan dan reduksi saya sangat mirip dengan Anda, beberapa tindakan menerima objek respons jaringan biasa sebagai argumen, dan saya merangkum logika bagaimana menangani data respons ke dalam tindakan dan akhirnya mengembalikan objek yang sangat sederhana sebagai nilai yang dikembalikan kemudian diteruskan ke peredam melalui panggilan pengiriman(). Sama seperti yang Anda lakukan. Masalahnya adalah jika tindakan Anda ditulis dengan cara ini, tindakan Anda telah melakukan hampir semua hal dan tanggung jawab peredam Anda akan sangat ringan, mengapa kami harus mentransfer data ke penyimpanan secara manual jika peredam hanya menyebarkan objek tindakan saja? Dosis redux secara otomatis bagi kami bukanlah hal yang sulit sama sekali.

Belum tentu. Tetapi sering kali, bagian dari proses bisnis
melibatkan memperbarui status aplikasi sesuai dengan aturan bisnis,
jadi Anda mungkin harus memasukkan beberapa logika bisnis di sana.

Untuk kasus ekstrim, lihat ini:
“Mesin RTS Sinkron dan Kisah Desinkronisasi” @ForrestTheWoods
https://blog.forrestthewoods.com/synchronous-rts-engines-and-a-tale-of-desyncs-9d8c3e48b2be

Pada 5 Apr 2016 17:54, "John Babak" [email protected] menulis:

Ini adalah manfaat dari menjaga sebagian besar logika pembaruan status di
peredam.

@dtinth https://github.com/dtinth Hanya untuk memperjelas, dengan mengatakan
"logika pembaruan keadaan" maksud Anda "logika bisnis"?


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung atau lihat di GitHub
https://github.com/reactjs/redux/issues/1171#issuecomment-205754910

@LumiaSaki Saran untuk menjaga reduksi Anda tetap sederhana sambil menjaga logika kompleks dalam tindakan pembuat bertentangan dengan cara yang disarankan untuk menggunakan Redux. Pola yang direkomendasikan Redux adalah kebalikannya: buat pembuat tindakan tetap sederhana sambil menjaga logika kompleks di reduksi. Tentu saja, Anda bebas untuk menempatkan semua logika Anda dalam pembuat tindakan, tetapi dengan melakukan itu Anda tidak mengikuti paradigma Redux, bahkan jika Anda menggunakan Redux.

Karena itu, Redux tidak akan secara otomatis mentransfer data dari tindakan ke penyimpanan. Karena itu bukan cara Anda seharusnya menggunakan Redux. Redux tidak akan diubah untuk memfasilitasi penggunaannya dengan cara lain dari yang dimaksudkan. Tentu saja, Anda benar-benar bebas melakukan apa yang sesuai untuk Anda, tetapi jangan berharap Redux mengubahnya.

Untuk apa nilainya, saya memproduksi reduksi saya menggunakan sesuatu seperti:

let {reducer, actions} = defineActions({
   fooRequest: (state, res) => ({...state, isFooing: true, toFoo: res.saidToFoo}),
   fooResponse: (state, res) => ({...state, isFooing: false, isFooed: true, fooData: res.data}),
   fooError: (state, res) => ({...state, isFooing: false, fooData: null, isFooed: false, fooError: res.error})
   fooReset: (state, res) => ({...state, isFooing: false, fooData: null, isFooed: false, fooError: null, toFoo: false})
})

defineActions mengembalikan peredam dan pembuat tindakan. Dengan cara ini saya merasa sangat mudah untuk menyimpan logika pembaruan saya di dalam peredam tanpa harus menghabiskan banyak waktu untuk menulis pembuat tindakan sepele.

Jika Anda bersikeras untuk menjaga logika Anda dalam pembuat tindakan Anda, maka Anda tidak akan kesulitan mengotomatisasi data sendiri. Peredam Anda adalah sebuah fungsi, dan ia dapat melakukan apa pun yang diinginkannya. Jadi peredam Anda bisa sesederhana:

function reducer(state, action) {
    if (action.data) {
        return {...state, ...action.data}
   } else {
        return state;
   }
}

Memperluas poin sebelumnya tentang logika bisnis, saya pikir Anda dapat memisahkan logika bisnis Anda menjadi dua bagian:

  • Bagian non-deterministik. Bagian ini menggunakan layanan eksternal, kode asinkron, waktu sistem, atau generator angka acak. Menurut pendapat saya bagian ini paling baik ditangani oleh fungsi I/O (atau pembuat tindakan). Saya menyebutnya proses bisnis.

Pertimbangkan perangkat lunak seperti IDE di mana pengguna dapat mengklik tombol jalankan, dan itu akan mengkompilasi dan menjalankan aplikasi. (Di sini saya menggunakan fungsi async yang mengambil toko, tetapi Anda dapat menggunakan redux-thunk sebagai gantinya.)

js export async function runApp (store) { try { store.dispatch({ type: 'startCompiling' }) const compiledApp = await compile(store) store.dispatch({ type: 'startRunning', app: compiledApp }) } catch (e) { store.dispatch({ type: 'errorCompiling', error: e }) } }

  • Bagian deterministik. Bagian ini memiliki hasil yang benar-benar dapat diprediksi. Mengingat keadaan yang sama dan peristiwa yang sama, hasilnya selalu dapat diprediksi. Menurut saya bagian ini paling baik ditangani oleh peredam. Saya menyebutnya aturan bisnis.

``` js
impor kamu dari 'updeep'

ekspor const peredam = createReducer({
// [nama tindakan]: tindakan => status saat ini => status berikutnya
startCompiling: () => u({ kompilasi: true }),
errorCompiling: ({ error }) => u({ kompilasi: false, compileError: error }),
startRunning: ({ aplikasi }) => u({
berjalan: () => aplikasi,
kompilasi: salah
}),
stopRunning: () => u({ running: false }),
buangCompileError: () => u({ compileError: null }),
// ...
})
```

Saya mencoba untuk memasukkan kode sebanyak mungkin ke tanah deterministik ini, sambil mengingat bahwa satu-satunya tanggung jawab peredam adalah menjaga status aplikasi tetap konsisten, mengingat tindakan yang masuk. Tidak ada lagi. Apa pun selain itu, saya akan melakukannya di luar Redux, karena Redux hanyalah wadah negara.

@dtinth Bagus, karena contoh sebelumnya di https://github.com/reactjs/redux/issues/1171#issuecomment -205782740 terlihat sangat berbeda dengan apa yang Anda tulis di https://github.com/reactjs/redux/ issue/1171#issuecomment -205888533 -- itu menyarankan untuk membangun bagian dari pembuat tindakan dalam keadaan dan meneruskannya ke reduksi agar mereka hanya menyebarkan pembaruan (pendekatan ini terlihat salah bagi saya, dan saya setuju dengan hal yang sama yang ditunjukkan dalam https://github.com/reactjs/redux/issues/1171#issuecomment-205865840 ).

@winstonewert

Pola yang direkomendasikan Redux adalah kebalikannya: buat pembuat tindakan tetap sederhana sambil menjaga logika kompleks di reduksi.

Bagaimana Anda bisa memasukkan logika kompleks ke dalam ruduser dan tetap membuatnya murni?

Jika saya memanggil fetch() misalnya dan memuat data dari server.. kemudian memprosesnya dengan cara tertentu. Saya belum melihat contoh peredam yang memiliki "logika kompleks"

@jayesbe : Uh... "kompleks" dan "murni" adalah ortogonal. Anda dapat memiliki _benar-benar_ logika kondisional atau manipulasi yang rumit di dalam peredam, dan selama itu hanya fungsi dari inputnya tanpa efek samping, itu masih murni.

Jika Anda memiliki keadaan lokal yang kompleks (pikirkan editor pos, tampilan hierarki, dll.) atau menangani hal-hal seperti pembaruan optimis, reduksi Anda akan berisi logika kompleks. Itu benar-benar tergantung pada aplikasi. Beberapa memiliki permintaan yang kompleks, yang lain memiliki pembaruan status yang kompleks. Ada yang punya keduanya :-)

@markerikson ok pernyataan logika adalah satu hal .. tetapi menjalankan tugas tertentu? Seperti katakanlah saya memiliki satu tindakan yang dalam satu kasus memicu tiga tindakan lain, atau dalam kasus lain memicu dua tindakan yang berbeda dan terpisah. Logika + eksekusi tugas itu sepertinya tidak harus menggunakan reduksi.

Status data/status model saya ada di server, status tampilan berbeda dari model data tetapi pengelolaan status itu ada di klien. Status model data saya hanya diturunkan ke tampilan .. itulah yang membuat reduksi dan tindakan saya sangat ramping.

@jayesbe : Saya tidak berpikir ada orang yang pernah mengatakan bahwa memicu tindakan lain harus menggunakan peredam. Dan sebenarnya, tidak seharusnya. Pekerjaan peredam hanyalah (currentState + action) -> newState .

Jika Anda perlu menyatukan beberapa tindakan, Anda melakukannya dalam sesuatu seperti thunk atau saga dan menjalankannya secara berurutan, atau memiliki sesuatu yang mendengarkan perubahan status, atau menggunakan middleware untuk mencegat suatu tindakan dan melakukan pekerjaan tambahan.

Saya agak bingung tentang apa diskusi pada saat ini, jujur ​​​​saja.

@markerikson topiknya sepertinya tentang "logika bisnis" dan ke mana

Seperti yang Anda perhatikan, yang terpenting adalah apa yang berhasil untuk Anda. Tapi inilah cara saya mengatasi masalah yang Anda tanyakan.

Jika saya memanggil fetch() misalnya dan memuat data dari server.. kemudian memprosesnya dengan cara tertentu. Saya belum melihat contoh peredam yang memiliki "logika kompleks"

Peredam saya mengambil respons mentah dari server saya dan memperbarui status saya dengannya. Dengan begitu respons pemrosesan yang Anda bicarakan dilakukan di peredam saya. Misalnya, permintaan mungkin mengambil catatan JSON untuk server saya, yang disimpan oleh peredam di cache catatan lokal saya.

k pernyataan logika adalah satu hal .. tetapi menjalankan tugas tertentu? Seperti katakanlah saya memiliki satu tindakan yang dalam satu kasus memicu tiga tindakan lain, atau dalam kasus lain memicu dua tindakan yang berbeda dan terpisah. Logika + eksekusi tugas itu sepertinya tidak harus menggunakan reduksi.

Itu tergantung pada apa yang Anda lakukan. Jelas, dalam kasus pengambilan server, satu tindakan akan memicu tindakan lainnya. Itu sepenuhnya dalam prosedur Redux yang direkomendasikan. Namun, Anda mungkin juga melakukan sesuatu seperti ini:

function createFoobar(dispatch, state, updateRegistry) {
   dispatch(createFoobarRecord());
   if (updateRegistry) {
      dispatch(updateFoobarRegistry());
   } else {
       dispatch(makeFoobarUnregistered());
   }
   if (hasFoobarTemps(state)) {
      dispatch(dismissFoobarTemps());
   }
}

Ini bukan cara yang disarankan untuk menggunakan Redux. Cara Redux yang disarankan adalah memiliki satu tindakan CREATE_FOOBAR yang menyebabkan semua perubahan yang diinginkan ini.

@winstonewert :

Ini bukan cara yang disarankan untuk menggunakan Redux. Cara Redux yang disarankan adalah memiliki satu tindakan CREATE_FOOBAR yang menyebabkan semua perubahan yang diinginkan ini.

Anda memiliki penunjuk ke suatu tempat yang ditentukan? Karena ketika saya melakukan riset untuk halaman FAQ, yang saya dapatkan adalah "itu tergantung", langsung dari Dan. Lihat http://redux.js.org/docs/FAQ.html#actions -multiple-actions dan jawaban ini oleh Dan di SO .

"Logika bisnis" sebenarnya adalah istilah yang cukup luas. Itu dapat mencakup hal-hal seperti "Apakah sesuatu telah terjadi?", "Apa yang kita lakukan sekarang setelah ini terjadi?", "Apakah ini valid?", dan seterusnya. Berdasarkan desain Redux, pertanyaan-pertanyaan itu _dapat_ dijawab di berbagai tempat tergantung pada situasinya, meskipun saya akan melihat "apakah itu terjadi" sebagai lebih dari tanggung jawab pembuat tindakan, dan "apa sekarang" hampir pasti merupakan tanggung jawab peredam.

Secara keseluruhan, pendapat saya tentang _seluruh pertanyaan_ tentang "logika bisnis" ini adalah: _"tergantung _". Ada alasan mengapa Anda mungkin ingin melakukan penguraian permintaan di pembuat tindakan, dan alasan mengapa Anda mungkin ingin melakukannya di peredam. Ada kalanya peredam Anda mungkin hanya "mengambil objek ini dan menamparnya ke keadaan saya", dan di lain waktu ketika peredam Anda mungkin merupakan logika kondisional yang sangat kompleks. Ada kalanya pembuat tindakan Anda mungkin sangat sederhana, dan ada kalanya bisa rumit. Ada kalanya masuk akal untuk mengirimkan beberapa tindakan dalam satu baris untuk mewakili langkah-langkah suatu proses, dan di lain waktu ketika Anda hanya ingin mengirimkan tindakan "THING_HAPPENED" generik untuk mewakili semuanya.

Tentang satu-satunya aturan keras dan cepat yang saya setujui adalah "non-determinisme dalam pencipta aksi, determinisme murni pada reduksi". Itu diberikan.

Selain daripada itu? Temukan sesuatu yang cocok untuk Anda. Konsisten. Ketahui mengapa Anda melakukannya dengan cara tertentu. Pergi dengan itu.

meskipun saya akan melihat "apakah itu terjadi" sebagai lebih dari tanggung jawab pencipta tindakan, dan "apa sekarang" hampir pasti merupakan tanggung jawab peredam.

Itulah mengapa ada diskusi paralel bagaimana menempatkan efek samping, yaitu bagian tidak murni dari "apa sekarang", ke dalam reduksi: #1528 dan menjadikannya hanya deskripsi murni tentang apa yang harus terjadi, seperti tindakan selanjutnya untuk dikirim.

Pola yang saya gunakan adalah:

  • Yang Harus Dilakukan : Pencipta Aksi/Aksi
  • Cara Melakukannya : Middleware (misalnya, middleware yang mendengarkan tindakan 'async' dan melakukan panggilan ke objek API saya.)
  • Apa Yang Harus Dilakukan Dengan Hasil : Peredam

Dari sebelumnya di utas ini, pernyataan Dan adalah:

Jadi rekomendasi resmi kami adalah Anda harus terlebih dahulu mencoba agar reduksi yang berbeda merespons tindakan yang sama. Jika canggung, maka tentu saja, buat pembuat tindakan terpisah. Tapi jangan mulai dengan pendekatan ini.

Dari situ, saya menganggap bahwa pendekatan yang disarankan adalah mengirimkan satu tindakan per acara. Tapi, secara pragmatis, lakukan apa yang berhasil.

@winstonewert : Dan mengacu pada pola "komposisi peredam", yaitu, "adalah tindakan yang hanya pernah didengarkan oleh satu peredam" vs "banyak reduksi dapat merespons tindakan yang sama". Dan sangat besar pada reduksi sewenang-wenang yang menanggapi satu tindakan. Yang lain lebih suka hal-hal seperti pendekatan "bebek", di mana reduksi dan tindakan SANGAT erat dibundel, dan hanya satu peredam yang pernah menangani tindakan yang diberikan. Jadi, contoh itu bukan tentang "mengirim banyak tindakan secara berurutan", melainkan "berapa banyak bagian dari struktur peredam saya yang diharapkan untuk merespons ini".

Tapi, secara pragmatis, lakukan apa yang berhasil.

:+1:

@sompylasar Saya melihat kesalahan cara saya dengan memiliki struktur status di Tindakan saya. Saya dapat dengan mudah mengubah struktur status menjadi reduksi dan menyederhanakan tindakan saya. Bersulang.

Sepertinya saya bahwa itu adalah hal yang sama.

Entah Anda memiliki satu tindakan yang memicu beberapa reduksi yang menyebabkan beberapa perubahan status, atau Anda memiliki beberapa tindakan yang masing-masing memicu satu peredam yang menyebabkan satu perubahan status. Memiliki beberapa reduksi yang merespons suatu tindakan dan memiliki beberapa tindakan yang mengirimkan beberapa tindakan adalah solusi alternatif untuk masalah yang sama.

Dalam pertanyaan StackOverflow yang Anda sebutkan, dia menyatakan:

Simpan log tindakan sedekat mungkin dengan riwayat interaksi pengguna. Namun jika itu membuat reduksi sulit untuk diterapkan, pertimbangkan untuk membagi beberapa tindakan menjadi beberapa, jika pembaruan UI dapat dianggap sebagai dua operasi terpisah yang kebetulan bersamaan.

Seperti yang saya lihat, Dan mendukung mempertahankan satu tindakan per interaksi pengguna sebagai cara yang ideal. Tapi dia pragmatis, ketika itu membuat peredam sulit untuk diterapkan, dia mendukung pemisahan tindakan.

Saya memvisualisasikan beberapa kasus penggunaan yang serupa tetapi agak berbeda di sini:

1) Suatu tindakan memerlukan pembaruan ke beberapa area di negara bagian Anda, terutama jika Anda menggunakan combineReducers untuk memiliki fungsi peredam terpisah yang menangani setiap sub-domain. Apakah kamu:

  • minta Peredam A dan Peredam B merespons tindakan yang sama dan memperbarui bit statusnya secara independen
  • minta barang-barang Anda SEMUA data yang relevan ke dalam tindakan sehingga peredam apa pun dapat mengakses bit status lain di luar potongannya sendiri
  • tambahkan peredam tingkat atas lain yang mengambil bit status Peredam A dan kasus khusus menyerahkannya ke Peredam B?
  • mengirimkan tindakan yang ditujukan untuk Peredam A dengan bit status yang dibutuhkannya, dan tindakan kedua untuk Peredam B dengan apa yang dibutuhkannya?

2) Anda memiliki serangkaian langkah yang terjadi dalam urutan tertentu, setiap langkah memerlukan beberapa pembaruan status atau tindakan middleware. Apakah kamu:

  • Kirim setiap langkah individu sebagai tindakan terpisah untuk mewakili kemajuan, dan biarkan reduksi individu merespons tindakan tertentu?
  • Kirim satu acara besar, dan percaya bahwa reduksi menanganinya dengan tepat?

Jadi ya, pasti ada yang tumpang tindih, tapi saya pikir bagian dari perbedaan gambaran mental di sini adalah berbagai kasus penggunaan.

@markerikson Jadi saran Anda adalah 'itu tergantung pada situasi apa yang Anda temui', dan bagaimana menyeimbangkan 'logika bisnis' pada tindakan atau reduksi hanya sesuai dengan pertimbangan Anda, kami juga harus memanfaatkan fungsi murni sebanyak mungkin?

Ya. Reducer _have_ menjadi murni, sebagai persyaratan Redux (kecuali dalam 0,00001% kasus khusus). Pembuat tindakan benar-benar _tidak _ harus murni, dan pada kenyataannya adalah tempat sebagian besar "kotoran" Anda akan hidup. Namun, karena fungsi murni jelas lebih mudah dipahami dan diuji daripada fungsi tidak murni, _if_ Anda dapat membuat beberapa logika pembuatan tindakan Anda murni, bagus! Jika tidak, tidak apa-apa.

Dan ya, dari sudut pandang saya, terserah Anda sebagai pengembang untuk menentukan keseimbangan yang tepat untuk logika aplikasi Anda sendiri dan di mana ia tinggal. Tidak ada satu aturan keras dan cepat untuk sisi mana dari pembuat aksi / peredam yang membaginya harus hidup. (Erm, kecuali untuk hal "determinisme / non-determinisme" yang saya sebutkan di atas. Yang dengan jelas saya maksudkan untuk referensi dalam komentar ini. Jelas.)

@cpsubrian

Apa Yang Harus Dilakukan Dengan Hasil: Peredam

Sebenarnya inilah mengapa saga adalah untuk: menangani efek seperti "jika ini terjadi, maka itu juga harus terjadi"


@markerikson @LumiaSaki

Pembuat aksi sama sekali tidak harus murni, dan pada kenyataannya adalah tempat sebagian besar "kotoran" Anda akan hidup.

Sebenarnya pencipta tindakan bahkan tidak diharuskan untuk menjadi tidak murni atau bahkan ada.
Lihat http://stackoverflow.com/a/34623840/82609

Dan ya, dari sudut pandang saya, terserah Anda sebagai pengembang untuk menentukan keseimbangan yang tepat untuk logika aplikasi Anda sendiri dan di mana ia tinggal. Tidak ada satu aturan keras dan cepat untuk sisi mana dari pembuat aksi / peredam yang membaginya harus hidup.

Ya, tetapi tidak begitu jelas untuk memperhatikan kekurangan dari setiap pendekatan tanpa pengalaman :) Lihat juga komentar saya di sini: https://github.com/reactjs/redux/issues/1171#issuecomment -167585575

Tidak ada aturan ketat yang berfungsi dengan baik untuk sebagian besar aplikasi sederhana, tetapi jika Anda ingin membuat komponen yang dapat digunakan kembali, komponen ini tidak boleh mengetahui sesuatu di luar cakupannya sendiri.

Jadi, alih-alih mendefinisikan daftar tindakan global untuk seluruh aplikasi, Anda dapat mulai membagi aplikasi menjadi komponen yang dapat digunakan kembali dan setiap komponen memiliki daftar tindakannya sendiri, dan hanya dapat mengirimkan/menguranginya. Masalahnya kemudian, bagaimana Anda mengekspresikan "ketika tanggal dipilih di pemilih tanggal saya, maka kita harus menyimpan pengingat pada item yang harus dilakukan, menunjukkan roti umpan balik, dan kemudian menavigasi aplikasi ke todos dengan pengingat": ini adalah tempat saga mulai beraksi: mengatur komponen

Lihat juga https://github.com/slorber/scalable-frontend-with-elm-or-redux

Dan ya, dari sudut pandang saya, terserah Anda sebagai pengembang untuk menentukan keseimbangan yang tepat untuk logika aplikasi Anda sendiri dan di mana ia tinggal. Tidak ada satu aturan keras dan cepat untuk sisi mana dari pembuat aksi / peredam yang membaginya harus hidup.

Ya, tidak ada persyaratan dari Redux apakah Anda memasukkan logika Anda ke dalam reduksi atau pembuat tindakan. Redux tidak akan rusak. Tidak ada aturan keras dan cepat yang mengharuskan Anda melakukannya dengan satu atau lain cara. Tapi rekomendasi Dan adalah untuk "Simpan log tindakan sedekat mungkin dengan riwayat interaksi pengguna yang Anda bisa." Mengirimkan satu tindakan per peristiwa pengguna tidak diperlukan, tetapi disarankan.

Dalam kasus saya, saya memiliki 2 reduksi yang tertarik pada 1 tindakan. Action.data mentah tidak cukup. Mereka perlu menangani data yang diubah. Saya tidak ingin melakukan transformasi di 2 reduksi. Jadi saya memindahkan fungsi untuk melakukan transformasi ke thunk. Dengan cara ini reduksi saya menerima data yang siap untuk dikonsumsi. Ini adalah yang terbaik yang bisa saya pikirkan dalam pengalaman redux 1 bulan singkat saya.

Bagaimana dengan memisahkan komponen/tampilan dari struktur toko? tujuan saya adalah bahwa apa pun yang dipengaruhi oleh struktur toko harus dikelola di reduksi, itu sebabnya saya suka menempatkan penyeleksi dengan reduksi, jadi komponen tidak benar-benar perlu tahu cara mendapatkan simpul toko tertentu.

Itu bagus untuk meneruskan data ke komponen, bagaimana dengan sebaliknya, ketika komponen mengirimkan tindakan:

Katakanlah misalnya dalam aplikasi Todo saya memperbarui nama item Todo, jadi saya mengirimkan tindakan yang melewati bagian dari item yang ingin saya perbarui yaitu:

dispatch(updateItem({name: <text variable>}));

, dan definisi tindakan adalah:

const updateItem = (updatedData) => {type: "UPDATE_ITEM", updatedData}

yang pada gilirannya ditangani oleh peredam yang hanya dapat melakukan:

Object.assign({}, item, action.updatedData)

untuk memperbarui item.

Ini berfungsi dengan baik karena saya dapat menggunakan kembali tindakan dan peredam yang sama untuk memperbarui prop apa pun dari item Todo, yaitu:

updateItem({description: <text variable>})

ketika deskripsi diubah sebagai gantinya.

Tetapi di sini komponen perlu tahu bagaimana item Todo didefinisikan di toko dan jika definisi itu berubah, saya harus ingat untuk mengubahnya di semua komponen yang bergantung padanya, yang jelas merupakan ide yang buruk, ada saran untuk skenario ini?

@dcoellarb

Solusi saya dalam situasi seperti ini adalah memanfaatkan fleksibilitas Javascript untuk menghasilkan apa yang akan menjadi boilerplate.

Jadi saya mungkin memiliki:

const {reducer, actions, selector} = makeRecord({
    name: TextField,
    description: TextField,
    completed: BooleanField
})

Di mana makeRecord adalah fungsi untuk secara otomatis membangun reduksi, pembuat tindakan, dan penyeleksi dari deskripsi saya. Itu menghilangkan boilerplate, tetapi jika saya perlu melakukan sesuatu yang tidak sesuai dengan pola rapi ini nanti, saya dapat menambahkan peredam/tindakan/pemilih khusus ke hasil makeRecord.

tks @winstonewert saya suka pendekatan untuk menghindari boilerplate, saya dapat melihat bahwa menghemat banyak waktu di aplikasi dengan banyak model; tetapi saya masih tidak melihat bagaimana ini akan memisahkan komponen dari struktur toko, maksud saya meskipun tindakan dihasilkan, komponen masih harus meneruskan bidang yang diperbarui ke sana, yang berarti bahwa komponen masih perlu mengetahui struktur toko kan?

@winstonewert @dcoellarb Menurut pendapat saya, struktur muatan tindakan harus milik tindakan, bukan ke reduksi, dan secara eksplisit diterjemahkan ke dalam struktur status di peredam. Seharusnya kebetulan yang beruntung bahwa struktur ini saling mencerminkan untuk kesederhanaan awal. Kedua struktur ini tidak perlu selalu mencerminkan satu sama lain karena mereka bukan entitas yang sama.

@sompylasar benar, saya melakukan itu, saya menerjemahkan data api/sisa ke struktur toko saya, tetapi masih satu-satunya yang harus mengetahui struktur toko adalah reduksi dan penyeleksi kan? alasan mengapa saya menempatkannya, masalah saya adalah dengan komponen/tampilan, saya lebih suka mereka tidak perlu mengetahui struktur toko jika saya memutuskan untuk mengubahnya nanti, tetapi seperti yang dijelaskan dalam contoh saya, mereka perlu mengetahui strukturnya sehingga mereka bisa kirim data yang tepat untuk diperbarui, saya belum menemukan cara yang lebih baik untuk melakukannya :(.

@dcoellarb Anda mungkin menganggap pandangan Anda sebagai input data tipe tertentu (seperti string atau angka, tetapi objek terstruktur dengan bidang). Objek data ini tidak selalu mencerminkan struktur penyimpanan. Anda menempatkan objek data ini ke dalam tindakan. Ini tidak sesuai dengan struktur toko. Baik penyimpanan maupun tampilan harus digabungkan dengan struktur muatan aksi.

@sompylasar masuk akal, saya akan mencobanya, terima kasih banyak!!!

Saya mungkin juga harus menambahkan bahwa Anda dapat membuat tindakan lebih murni dengan menggunakan redux-saga . Namun, redux-saga kesulitan menangani peristiwa asinkron, jadi Anda dapat mengambil ide ini selangkah lebih maju dengan menggunakan RxJS (atau pustaka FRP apa pun) alih-alih redux-saga. Berikut ini contoh menggunakan KefirJS: https://github.com/awesome-editor/awesome-editor/blob/saga-demo/src/stores/app/AppSagaHandlers.js

Hai @frankandrobot ,

redux-saga berjuang untuk menangani acara async

Apa yang Anda maksud dengan ini? Bukankah redux-saga dibuat untuk menangani peristiwa asinkron dan efek samping dengan cara yang elegan? Lihatlah https://github.com/reactjs/redux/issues/1171#issuecomment -167715393

Tidak ada @IceOnFire . Terakhir kali saya membaca dokumen redux-saga , menangani alur kerja async yang kompleks itu sulit. Lihat, misalnya: http://yelouafi.github.io/redux-saga/docs/advanced/NonBlockingCalls.html
Dikatakan (masih mengatakan?) sesuatu yang berpengaruh

kami akan menyerahkan sisa detailnya kepada pembaca karena mulai rumit

Bandingkan dengan cara FRP: https://github.com/frankandrobot/rflux/blob/master/doc/06-sideeffects.md#a -more-complex-workflow
Seluruh alur kerja itu ditangani sepenuhnya. (Hanya dalam beberapa baris yang bisa saya tambahkan.) Selain itu, Anda masih mendapatkan sebagian besar kebaikan redux-saga (semuanya adalah fungsi murni, sebagian besar unit test yang mudah).

Terakhir kali saya memikirkan hal ini, saya sampai pada kesimpulan bahwa masalahnya adalah redux-saga membuat semuanya terlihat sinkron. Ini bagus untuk alur kerja sederhana tetapi untuk alur kerja asinkron yang kompleks, lebih mudah jika Anda menangani asinkron secara eksplisit... yang merupakan keunggulan FRP.

Hai @frankandrobot ,

Terima kasih atas penjelasan Anda. Saya tidak melihat kutipan yang Anda sebutkan, mungkin perpustakaan berevolusi (misalnya, sekarang saya melihat efek cancel belum pernah saya lihat sebelumnya).

Jika dua contoh (saga dan FRP) berperilaku persis sama maka saya tidak melihat banyak perbedaan: satu adalah urutan instruksi di dalam blok coba/tangkap, sementara yang lain adalah rantai metode pada aliran. Karena kurangnya pengalaman saya di aliran, saya bahkan menemukan contoh saga yang lebih mudah dibaca, dan lebih dapat diuji karena Anda dapat menguji setiap yield satu per satu. Tapi saya cukup yakin ini karena pola pikir saya lebih dari teknologi.

Pokoknya saya ingin tahu pendapat @yelouafi tentang ini.

@bvaughn dapatkah Anda menunjukkan contoh tindakan pengujian, peredam, pemilih yang layak dalam pengujian yang sama seperti yang Anda jelaskan di sini?

Cara paling efisien untuk menguji aksi, reduksi, dan pemilih adalah dengan mengikuti pendekatan "bebek" saat menulis tes. Ini berarti Anda harus menulis satu set pengujian yang mencakup serangkaian tindakan, reduksi, dan pemilih tertentu, bukan 3 set pengujian yang berfokus pada masing-masing individu. Ini lebih akurat mensimulasikan apa yang terjadi di aplikasi Anda yang sebenarnya dan memberikan hasil maksimal.

Hai @ morgs32

Masalah ini agak lama dan saya sudah lama tidak menggunakan Redux. Ada bagian di situs Redux tentang Tes Menulis yang mungkin ingin Anda periksa.

Pada dasarnya saya hanya menunjukkan bahwa- daripada menulis tes untuk tindakan dan reduksi secara terpisah- dapat lebih efisien untuk menulisnya bersama-sama, seperti:

import configureMockStore from 'redux-mock-store'
import { actions, selectors, reducer } from 'your-redux-module';

it('should support adding new todo items', () => {
  const mockStore = configureMockStore()
  const store = mockStore({
    todos: []
  })

  // Start with a known state
  expect(selectors.todos(store.getState())).toEqual([])

  // Dispatch an action to modify the state
  store.dispatch(actions.addTodo('foo'))

  // Verify the action & reducer worked successfully using your selector
  // This has the added benefit of testing the selector also
  expect(selectors.todos(store.getState())).toEqual(['foo'])
})

Ini hanya pengamatan saya sendiri setelah menggunakan Redux selama beberapa bulan pada sebuah proyek. Itu bukan rekomendasi resmi. YMMV. 👍

"Lakukan lebih banyak di pembuat aksi dan lebih sedikit di reduksi"

Bagaimana jika aplikasi adalah server&klien dan server harus berisi logika bisnis dan validator?
Jadi saya mengirim tindakan apa adanya, dan sebagian besar pekerjaan akan dilakukan di sisi server oleh peredam...

Pertama, Maaf untuk bahasa Inggris saya. Tapi saya punya beberapa pendapat yang berbeda.

Pilihan saya adalah fat peredam, thin pencipta tindakan.

Pembuat tindakan saya hanya __dispatch__ actions(async, sync, serial-async, parallel-async, parallel-async in for-loop) berdasarkan beberapa __promise middleware__ .

Pereduksi saya dibagi menjadi banyak irisan negara kecil untuk menangani logika bisnis. gunakan combineReduers menggabungkannya. reducer adalah __fungsi murni__, sehingga mudah digunakan kembali. Mungkin suatu hari nanti saya menggunakan angularJS, saya pikir saya dapat menggunakan kembali reducer saya di layanan saya untuk logika bisnis yang sama. Jika reducer memiliki banyak kode baris, itu mungkin dapat dipecah menjadi peredam yang lebih kecil atau abstrak beberapa fungsi.

Ya, ada beberapa kasus lintas negara yang artinya A bergantung pada B, C .. dan B, C adalah data asinkron. Kita harus menggunakan B,C untuk mengisi atau menginisialisasi A. Oleh karena itu saya menggunakan crossSliceReducer .

Tentang __Lakukan lebih banyak di pembuat aksi dan lebih sedikit di reduksi__.

  1. Jika Anda menggunakan redux-thunk atau dll. Ya. Anda dapat mengakses status lengkap dalam pembuat tindakan dengan getState() . Ini adalah pilihan. Atau, Anda dapat membuat beberapa __crossSliceReducer__, sehingga Anda juga dapat mengakses status lengkap, Anda dapat menggunakan beberapa irisan status untuk menghitung status lainnya.

Tentang __Pengujian unit__

reducer adalah __fungsi murni__. Jadi mudah untuk diuji. Untuk saat ini, saya hanya menguji reduksi saya, karena itu paling penting daripada bagian lain.

Untuk menguji action creators ? Saya pikir jika mereka "gemuk", mungkin tidak mudah untuk diuji. Terutama __async action creators__.

Saya setuju dengan Anda @mrdulin, begitulah cara saya sekarang.

@mrdulin Ya. Sepertinya middleware adalah tempat yang tepat untuk menempatkan logika tidak murni Anda.
Tetapi untuk peredam logika bisnis sepertinya bukan tempat yang tepat. Anda akan berakhir dengan beberapa tindakan "sintetis" yang tidak mewakili apa yang diminta pengguna tetapi apa yang dibutuhkan logika bisnis Anda.

Pilihan yang jauh lebih sederhana hanyalah memanggil beberapa fungsi/metode kelas murni dari middleware:

middleware = (...) => {
  // if(action.type == 'HIGH_LEVEL') 
  handlers[action.name]({ dispatch, params: action.payload })
}
const handlers = {
  async highLevelAction({ dispatch, params }) {
    dispatch({ loading: true });
    const data = await api.getData(params.someId);
    const processed = myPureLogic(data);
    dispatch({ loading: false, data: processed });
  }
}

@bvaughn

Ini mengurangi kemungkinan variabel salah ketik yang mengarah ke nilai tidak terdefinisi yang halus, menyederhanakan perubahan pada struktur toko Anda, dll.

Kasus saya terhadap penggunaan penyeleksi di mana-mana, bahkan untuk bagian sepele yang tidak memerlukan memoisasi atau transformasi data apa pun:

  • Bukankah pengujian unit untuk reduksi seharusnya sudah menangkap properti status yang salah ketik?
  • Perubahan pada struktur toko tidak sering terjadi dalam pengalaman saya, sebagian besar pada fase peningkatan proyek. Kemudian, jika Anda mengubah state.stuff.item1 menjadi state.stuff.item2 Anda mencari melalui kode dan mengubahnya di mana-mana - sama seperti mengubah nama apa pun. Ini adalah tugas umum dan non-brainer bagi orang-orang yang menggunakan IDE khususnya.
  • Menggunakan penyeleksi di mana-mana adalah semacam abstraksi kosong. sederhana dari negara secara langsung. Tentu, Anda mendapatkan konsistensi dengan memiliki API ini untuk mengakses status, tetapi Anda menyerah pada kesederhanaan.

Jelas penyeleksi diperlukan, tetapi saya ingin mendengar beberapa argumen lain untuk menjadikannya API wajib.

Dari sudut pandang praktis, satu alasan bagus dalam pengalaman saya adalah bahwa perpustakaan seperti memilih ulang atau redux-saga memanfaatkan pemilih untuk mengakses bagian negara. Ini cukup bagi saya untuk tetap menggunakan penyeleksi.

Secara filosofis, saya selalu melihat penyeleksi sebagai "metode pengambil" dari dunia fungsional. Dan untuk alasan yang sama mengapa saya tidak akan pernah mengakses atribut publik dari objek Java, saya tidak akan pernah mengakses substat secara langsung di aplikasi Redux.

@IceOnFire Tidak ada yang bisa dimanfaatkan jika perhitungannya tidak mahal atau transformasi data tidak diperlukan.

Metode pengambil mungkin merupakan praktik umum di Java tetapi begitu juga mengakses POJO secara langsung di JS.

@timotgl

Mengapa ada API di antara toko dan kode redux lainnya?

Selector adalah API publik kueri (baca) peredam, tindakan adalah API publik perintah (tulis) peredam. Struktur Reducer adalah detail implementasinya.

Selektor dan tindakan digunakan di lapisan UI, dan di lapisan saga (jika Anda menggunakan redux-saga), bukan di peredam itu sendiri.

@sompylasar Tidak yakin saya mengikuti sudut pandang Anda di sini. Tidak ada alternatif untuk tindakan, saya harus menggunakannya untuk berinteraksi dengan redux. Saya tidak harus menggunakan penyeleksi namun, saya hanya dapat memilih sesuatu langsung dari keadaan ketika terbuka, yang dengan desain.

Anda menjelaskan cara untuk memikirkan penyeleksi sebagai API "baca" peredam, tetapi pertanyaan saya adalah apa yang membenarkan menjadikan penyeleksi sebagai API wajib di tempat pertama (wajib seperti dalam menegakkan itu sebagai praktik terbaik dalam sebuah proyek, bukan oleh perpustakaan desain).

@timotgl Ya, penyeleksi tidak wajib. Tetapi mereka melakukan praktik yang baik untuk mempersiapkan perubahan di masa mendatang dalam kode aplikasi, memungkinkan untuk memfaktorkannya secara terpisah:

  • bagaimana bagian tertentu dari negara disusun dan ditulis (peredam perhatian, satu tempat)
  • bagaimana bagian negara itu digunakan (UI dan masalah efek samping, banyak tempat di mana bagian negara yang sama ditanyakan)

Saat Anda akan mengubah struktur penyimpanan, tanpa penyeleksi, Anda harus menemukan dan memfaktorkan ulang semua tempat di mana bagian yang terpengaruh diakses, dan ini berpotensi menjadi tugas non-sepele, bukan hanya menemukan-dan- ganti, terutama jika Anda menyebarkan fragmen status yang diperoleh langsung dari toko, bukan melalui pemilih.

@sompylasar Terima kasih atas masukan Anda.

ini berpotensi menjadi tugas non-sepele

Itu kekhawatiran yang valid, sepertinya pengorbanan yang mahal bagi saya. Saya kira saya belum menemukan refactoring negara yang menyebabkan masalah seperti itu. Saya telah menemukan "spageti pemilih", di mana pemilih bersarang untuk setiap sub-bagian sepele menyebabkan kebingungan. Tindakan balasan ini sendiri harus dipertahankan juga. Tapi saya mengerti alasan di balik ini lebih baik sekarang.

@timotgl Contoh sederhana yang dapat saya bagikan secara publik:

export const PROMISE_REDUCER_STATE_IDLE = 'idle';
export const PROMISE_REDUCER_STATE_PENDING = 'pending';
export const PROMISE_REDUCER_STATE_SUCCESS = 'success';
export const PROMISE_REDUCER_STATE_ERROR = 'error';

export const PROMISE_REDUCER_STATES = [
  PROMISE_REDUCER_STATE_IDLE,
  PROMISE_REDUCER_STATE_PENDING,
  PROMISE_REDUCER_STATE_SUCCESS,
  PROMISE_REDUCER_STATE_ERROR,
];

export const PROMISE_REDUCER_ACTION_START = 'start';
export const PROMISE_REDUCER_ACTION_RESOLVE = 'resolve';
export const PROMISE_REDUCER_ACTION_REJECT = 'reject';
export const PROMISE_REDUCER_ACTION_RESET = 'reset';

const promiseInitialState = { state: PROMISE_REDUCER_STATE_IDLE, valueOrError: null };
export function promiseReducer(state = promiseInitialState, actionType, valueOrError) {
  switch (actionType) {
    case PROMISE_REDUCER_ACTION_START:
      return { state: PROMISE_REDUCER_STATE_PENDING, valueOrError: null };
    case PROMISE_REDUCER_ACTION_RESOLVE:
      return { state: PROMISE_REDUCER_STATE_SUCCESS, valueOrError: valueOrError };
    case PROMISE_REDUCER_ACTION_REJECT:
      return { state: PROMISE_REDUCER_STATE_ERROR, valueOrError: valueOrError };
    case PROMISE_REDUCER_ACTION_RESET:
      return { ...promiseInitialState };
    default:
      return state;
  }
}

export function extractPromiseStateEnum(promiseState = promiseInitialState) {
  return promiseState.state;
}
export function extractPromiseStarted(promiseState = promiseInitialState) {
  return (promiseState.state === PROMISE_REDUCER_STATE_PENDING);
}
export function extractPromiseSuccess(promiseState = promiseInitialState) {
  return (promiseState.state === PROMISE_REDUCER_STATE_SUCCESS);
}
export function extractPromiseSuccessValue(promiseState = promiseInitialState) {
  return (promiseState.state === PROMISE_REDUCER_STATE_SUCCESS ? promiseState.valueOrError || null : null);
}
export function extractPromiseError(promiseState = promiseInitialState) {
  return (promiseState.state === PROMISE_REDUCER_STATE_ERROR ? promiseState.valueOrError || true : null);
}

Bukan masalah pengguna peredam ini apakah yang disimpan adalah state, valueOrError atau yang lainnya. Terkena adalah string status (enum), beberapa pemeriksaan yang sering digunakan pada status itu, nilai dan kesalahannya.

Saya telah menemukan "spageti pemilih", di mana pemilih bersarang untuk setiap sub-bagian sepele menyebabkan kebingungan.

Jika sarang ini disebabkan oleh pencerminan sarang (komposisi) peredam, bukan itu yang saya rekomendasikan, itu detail implementasi peredam itu. Dengan menggunakan contoh di atas, reduksi yang menggunakan janjiReducer untuk beberapa bagian dari negara bagian mereka mengekspor penyeleksi mereka sendiri yang diberi nama sesuai dengan bagian ini. Juga tidak setiap fungsi yang terlihat seperti pemilih harus diekspor dan menjadi bagian dari API peredam.

function extractTransactionLogPromiseById(globalState, transactionId) {
  return extractState(globalState).transactionLogPromisesById[transactionId] || undefined;
}

export function extractTransactionLogPromiseStateEnumByTransactionId(globalState, transactionId) {
  return extractPromiseStateEnum(extractTransactionLogPromiseById(globalState, transactionId));
}

export function extractTransactionLogPromiseErrorTransactionId(globalState, transactionId) {
  return extractPromiseError(extractTransactionLogPromiseById(globalState, transactionId));
}

export function extractTransactionLogByTransactionId(globalState, transactionId) {
  return extractPromiseSuccessValue(extractTransactionLogPromiseById(globalState, transactionId));
}

Oh, satu hal lagi yang hampir saya lupakan. Nama fungsi dan ekspor/impor dapat diperkecil dengan baik dan aman. Kunci objek bersarang — tidak terlalu banyak, Anda memerlukan pelacak aliran data yang tepat di minifier agar tidak mengacaukan kode.

@timotgl : banyak praktik terbaik yang kami dorong dengan Redux adalah tentang mencoba merangkum logika dan perilaku terkait Redux.

Misalnya, Anda dapat mengirimkan tindakan langsung dari komponen yang terhubung, dengan melakukan this.props.dispatch({type : "INCREMENT"}) . Namun, kami tidak menyarankannya, karena memaksa komponen untuk "mengetahui" bahwa ia sedang berbicara dengan Redux. Cara yang lebih React-idiomatik untuk melakukan sesuatu adalah dengan meneruskan pembuat tindakan terikat, sehingga komponen dapat memanggil this.props.increment() , dan tidak masalah apakah fungsi itu adalah pembuat tindakan Redux terikat, panggilan balik diteruskan diturunkan oleh orang tua, atau fungsi tiruan dalam tes.

Anda juga dapat menulis jenis tindakan dengan tangan di mana saja, tetapi kami menganjurkan untuk mendefinisikan variabel konstan sehingga variabel tersebut dapat diimpor, dilacak, dan mengurangi kemungkinan kesalahan ketik.

Demikian pula, tidak ada yang mencegah Anda mengakses state.some.deeply.nested.field di fungsi atau thunks mapState . Tetapi, seperti yang telah dijelaskan di utas ini, ini meningkatkan kemungkinan kesalahan ketik, membuatnya lebih sulit untuk melacak tempat-tempat bagian tertentu dari keadaan sedang digunakan, membuat refactoring lebih sulit, dan berarti bahwa logika transformasi mahal apa pun mungkin akan diubah. -berjalan setiap saat bahkan jika tidak perlu.

Jadi tidak, Anda tidak _memiliki_ untuk menggunakan penyeleksi, tetapi itu adalah praktik arsitektur yang baik.

Anda mungkin ingin membaca posting saya Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance .

@markerikson Saya tidak menentang penyeleksi secara umum, atau penyeleksi untuk perhitungan yang mahal. Dan saya tidak pernah bermaksud untuk mengirimkan pengiriman sendiri ke komponen :)

Maksud saya adalah bahwa saya tidak setuju dengan keyakinan ini:

Idealnya, hanya fungsi peredam dan penyeleksi Anda yang harus mengetahui struktur status yang tepat, jadi jika Anda mengubah tempat tinggal beberapa status, Anda hanya perlu memperbarui dua bagian logika tersebut.

Tentang contoh Anda dengan state.some.deeply.nested.field , saya dapat melihat nilai memiliki pemilih untuk mempersingkat ini. selectSomeDeeplyNestedField() adalah satu nama fungsi vs. 5 properti yang bisa saya salah.

Di sisi lain, jika Anda mengikuti pedoman ini untuk surat itu, Anda juga melakukan const selectSomeField = state => state.some.field; atau bahkan const selectSomething = state => state.something; , dan pada titik tertentu overhead (impor, ekspor, pengujian) melakukan ini secara konsisten tidak membenarkan keamanan dan kemurnian (dapat diperdebatkan) lagi menurut saya. Ini dimaksudkan dengan baik tetapi saya tidak bisa melepaskan semangat dogmatis dari pedoman ini. Saya akan mempercayai pengembang dalam proyek saya untuk menggunakan penyeleksi dengan bijak dan bila berlaku - karena pengalaman saya sejauh ini adalah bahwa mereka melakukan itu.

Dalam kondisi tertentu saya bisa melihat mengapa Anda ingin berbuat salah di sisi keselamatan dan konvensi. Terima kasih atas waktu dan keterlibatan Anda, saya secara keseluruhan sangat senang kami memiliki redux.

Tentu. FWIW, ada pustaka pemilih lain, dan juga pembungkus di sekitar Pilih Ulang juga. Misalnya, https://github.com/planttheidea/selectorator memungkinkan Anda menentukan jalur kunci notasi titik, dan itu melakukan penyeleksi perantara untuk Anda secara internal.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat