Redux: Praktik terbaik untuk memperbarui status dependen di berbagai cabang pohon negara?

Dibuat pada 18 Sep 2015  ·  31Komentar  ·  Sumber: reduxjs/redux

Dalam dokumen Reducer tertulis:

Sometimes state fields depend on one another and more consideration is required, but in our case we can easily split updating todos into a separate function..

Saya ingin tahu bagaimana menangani kasus penggunaan khusus itu. Sebagai contoh, katakan saya memiliki formulir dengan beberapa bidang data. Saya memiliki komponen lain di tempat lain yang harus bereaksi terhadap perubahan bentuk, misalnya mengubah status berdasarkan apakah data formulir valid, atau menghitung nilai dari input yang berbeda ke formulir dan menampilkannya.

Meskipun saya yakin saya dapat memiliki reduksi yang berbeda (yang menangani potongan berbeda dari pohon status) mendengarkan tindakan yang sama dan memperbarui dirinya sendiri, status "reaktif" tidak akan memiliki akses ke cabang lain dari pohon status yang diperlukannya untuk menghitung sendiri.

Salah satu solusi yang saya pikirkan adalah agar proses "bereaksi" terjadi dengan menggunakan subscribe untuk mendengarkan perubahan dalam pohon status, menghitung apakah ada perubahan yang relevan dibuat, lalu mengirimkan tindakan baru untuk memperbarui bidang dependen. Namun, ledakan penerbitan ini mungkin harus menjadi sangat umum, sehingga semua pereduksi yang peduli akan dapat menggunakan muatan yang menyertainya.

Saya baru mengenal Redux, jadi saya tidak yakin apakah ada pola yang diadopsi untuk hal semacam ini. Saya menghargai setiap saran atau petunjuk ke arah yang benar. Terima kasih.

question

Komentar yang paling membantu

Saat Anda memerlukan bagian berbeda dari pohon status untuk menyelesaikan efek tindakan tunggal yang tidak dapat ditangani dengan cara yang saling eksklusif di setiap cabang, gunakan reduce-reducers untuk menggabungkan peredam yang dihasilkan oleh combineReducer dengan Anda peredam lintas sektor:

export default reduceReducers(
  combineReducers({
    router: routerReducer,
    customers,
    stats,
    dates,
    filters,
    ui
  }),
  // cross-cutting concerns because here `state` is the whole state tree
  (state, action) => {
    switch (action.type) {
      case 'SOME_ACTION':
        const customers = state.customers;
        const filters = state.filters;
        // ... do stuff
    }
  }
);

Semua 31 komentar

Maaf, opsi lain yang saya temukan menggunakan rootReducer khusus. Secara teknis, peredam ini tidak harus berada di root; bisa saja Leluhur Umum Terendah dari bagian-bagian pohon negara bagian yang bergantung satu sama lain.

Jika data _dapat_ dihitung dari status "lebih rendah", jangan simpan di status tersebut. Gunakan penyeleksi memo seperti yang dijelaskan dalam Menghitung Data Berasal .

Jika memang tidak bisa, gunakan peredam tunggal yang mungkin (jika diinginkan) meneruskan parameter tambahan ke peredam atau menggunakan hasilnya:

function a(state, action) { }
function b(state, action, a) { } // depends on a's state

function something(state = {}, action) {
  let a = a(state.a, action);
  let b = b(state.b, action, a); // note: b depends on a for computation
  return { a, b };
}

Terima kasih @gaearon untuk cuplikan itu. Saya dengan cepat mengalami masalah ini sendiri ketika mencoba mem-port widget ke redux dan telah menghabiskan waktu mencoba menemukan solusi yang disetujui komunitas. Mungkin ini adalah sesuatu yang bisa lebih menonjol dalam dokumentasi karena menurut saya cukup umum untuk memiliki status dependen.

Tampaknya juga mudah untuk melewatkan seluruh pohon status sebagai parameter opsional ketiga di dalam fungsi combineReducers . Melakukan hal itu akan memfasilitasi masalah ini meskipun saya tidak cukup berpengalaman dengan redux untuk mengetahui apakah itu ide yang baik atau buruk. Pikiran?

Ada bagiannya sendiri di dokumen , jadi menurut saya ini cukup menonjol secara pribadi. :tersenyum:

Anda tidak perlu menggunakan combineReducers jika tidak mau. Ini adalah utilitas yang berguna, tetapi bukan persyaratan. Mungkin pengurangan-reduksi atau redux-tindakan lebih sesuai gaya Anda?

Jika data dapat dihitung dari status "lebih rendah", jangan simpan di status.

Ada bagiannya sendiri di dokumen, jadi menurut saya ini cukup menonjol secara pribadi.

Oke, saya pikir akhirnya saya berhasil. Terima kasih!

"Jika data dapat dihitung dari status" lebih rendah ", jangan simpan di negara bagian itu."

Bagaimana jika data hanya dapat dihitung dari status "lebih rendah" yang dikombinasikan dengan payload tindakan?

Saat Anda memerlukan bagian berbeda dari pohon status untuk menyelesaikan efek tindakan tunggal yang tidak dapat ditangani dengan cara yang saling eksklusif di setiap cabang, gunakan reduce-reducers untuk menggabungkan peredam yang dihasilkan oleh combineReducer dengan Anda peredam lintas sektor:

export default reduceReducers(
  combineReducers({
    router: routerReducer,
    customers,
    stats,
    dates,
    filters,
    ui
  }),
  // cross-cutting concerns because here `state` is the whole state tree
  (state, action) => {
    switch (action.type) {
      case 'SOME_ACTION':
        const customers = state.customers;
        const filters = state.filters;
        // ... do stuff
    }
  }
);

@neverfox Terima kasih untuk itu! persis seperti yang saya cari. Apakah Anda mengetahui cara lain untuk mengakses status root dari peredam gabungan atau merancang ulang sesuatu?

@tokopedia

Untuk contoh khusus Anda, saya tidak yakin mengapa tidak ingin membiarkan pereduksi yang berbeda menangani tindakan yang sama. Itulah gunanya menggunakan Redux. Silakan lihat https://gist.github.com/imton/cf6f40578524ddd085dd#gistcomment -1656424.

Saya tidak berpikir reduksi “lintas sektoral” umumnya merupakan ide yang bagus. Mereka memecahkan enkapsulasi pereduksi individu. Sulit untuk mengubah bentuk status internal karena beberapa kode lain di luar reduksi mungkin bergantung padanya.

Sebaliknya, kami merekomendasikan _not_ mengandalkan bentuk status di mana pun kecuali reduksi itu sendiri. Bahkan komponen dapat menghindari bergantung pada bentuk status jika Anda mengekspor "pemilih" (fungsi yang meminta status) dari file yang sama sebagai reduksi. Lihat contoh shopping-cart di repo untuk pendekatan ini.

Untuk memperjelas, saya hanya menyarankan peredam lintas sektoral untuk kasus-kasus yang tidak saling eksklusif, yaitu. tidak bisa begitu saja ditangani oleh pereduksi terpisah yang menanggapi tindakan yang sama. Misalnya, tindakan masuk dan perlu mengambil beberapa data dari satu cabang pohon (biasanya ditangani oleh peredam A) dan beberapa data dari yang lain (biasanya ditangani oleh peredam B) dan mendapatkan beberapa data status baru dari kombinasi keduanya keduanya (mungkin untuk disimpan di cabang C negara bagian lain). Jika ada cara untuk melakukan ini selain refactoring bentuk negara atau menggandakan data (yang tidak selalu layak atau diinginkan), maka saya pasti terbuka untuk saran, karena situasi seperti itu sering muncul saat aplikasi tumbuh dan baru fitur datang yang tidak memainkan desain keadaan asli.

Untuk menghitung data turunan, kami menyarankan menggunakan penyeleksi daripada menyimpannya di pohon status.

http://redux.js.org/docs/recipes/ComputingDerivedData.html

Yang bagus dan berguna, sampai perlu menjadi bagian dari negara.

Apa yang Anda maksud dengan "kebutuhan"? Dapatkah Anda menjelaskan skenario tertentu yang Anda maksud?

@gaearon , penasaran ingin mendengar pendapat Anda tentang skenario berikut ...

Saat Anda memasukkan karakter di bidang kata sandi, kami menjalankan aturan validasi berikut terhadap properti password state:

  • Apakah ada 3 atau lebih karakter yang berurutan?
  • Apakah ada spasi di password ?
  • Apakah username bagian dari password ?

Jika salah satu hal di atas benar, kami menampilkan kesalahan tingkat halaman dengan pesan kesalahan tingkat halaman tertentu yang sesuai dan, tergantung pada kesalahan yang mana, kami mungkin menampilkan pesan kesalahan tingkat bidang.

Jadi saya akan memiliki 3 pengurang, masing-masing 1 untuk username dan password , untuk menahan nilai string, dan 1 untuk notification , yang menangani pemberitahuan kesalahan tingkat halaman message dan type :

// reducers/notifcation.js
import { ENTER_PASSWORD } from "../../actions/CreateAccount/types";
import strings from "../../../../example/CreateAccount/strings_EN";



const DEFAULT_STATE = {
  type: '',
  message: ''
};

export default (state = DEFAULT_STATE, action) => {
  const { value: password, type } = action;

  if (type === ENTER_PASSWORD) {
    const errors = strings.errors.password;
    let message;

    if (password.length <= 3) {
      message = errors.tooShort;
    } else if (password.indexOf(username) !== -1) {
      message = errors.containsUsername;
    } else if (password.indexOf(' ') !== -1) {
      message = errors.containsSpace;
    }

    if (message) {
      return {
        message,
        type: 'error'
      };
    }
  }
  return DEFAULT_STATE;
};

Jadi dalam kasus ini, peredam notification perlu tahu tentang username dan password . Kita dapat mengakses password karena kita peduli dengan jenis tindakan ENTER_PASSWORD yang menyertakannya, tetapi, karena saat ini diatur, saya tidak akan mendapatkan username dan password bersama-sama. Secara teori saya juga harus memeriksa jenis tindakan ENTER_USERNAME untuk memastikan bahwa jika mereka telah memasukkan password dan kemudian kembali ke bidang nama pengguna dan memodifikasi username , lalu saya periksa untuk memastikan bahwa password tidak berisi username diperbarui ... Tapi kita bisa melewati itu untuk saat ini.

Pikiran?

Salah satu solusinya adalah mengelompokkan username dan password bersama-sama sebagai credentials , dan menggabungkan jenis tindakan ENTER_PASSWORD dan ENTER_USERNAME menjadi satu ENTER_CREDENTIALS pengiriman, tetapi untuk kotoran dan cekikikan, katakanlah itu tidak mungkin dilakukan dalam situasi ini.

Silakan lihat bentuk-redux dan bagaimana itu menangani skenario seperti ini. Secara umum, lebih baik mengajukan pertanyaan di StackOverflow daripada di sini. Saat ini saya tidak dapat memberikan jawaban panjang karena saya sibuk.

Saya membuat comboSectionReducers .
Ini menyelesaikannya dengan cara seperti combineReducers 'one.

Mungkin tahu sesuatu tentang ini? @gaearon https://github.com/omnidan/redux-undo/issues/102

Terima kasih!

@gaearon Diskusi dengan @neverfox tiba-tiba berhenti, tapi saya rasa saya tahu apa yang dia maksud - saya menemukan masalah yang sama:

Katakanlah kita memiliki peredam root yang disusun menggunakan combineReducers dengan cabang negara bagian: chat dan ui . Di dalam peredam khusus chat -kita perlu bereaksi terhadap tindakan _ (mis. Menerima pesan baru dari server) _ dengan cara bergantung pada status dari ui bagian _ (mis. msg hanya jika akhir daftar pesan ada di viewport - info itu disimpan di bagian ui ) _. Kita tidak dapat mengakses status dari ui branch di dalam chat branch - oleh karena itu tidak mungkin menggunakan selector.

Solusi pertama yang terlintas di benak saya adalah melakukan bagian bersyarat dalam pemikiran khusus dan dari tempat itu mengirimkan salah satu dari dua tindakan khusus. Namun ini memperkenalkan beberapa kreasi tambahan dan karena itu sangat mengaburkan apa yang sedang terjadi.

Untuk alasan ini saya menemukan solusi @neverfox yang paling bersih, meskipun, seperti yang Anda katakan, itu merusak enkapsulasi. Apakah Anda memiliki pendapat / solusi lain tentang masalah ini setelah menyatakan masalah seperti yang saya lakukan di atas?

@jalooc : FAQ Redux membahas masalah ini, di http://redux.js.org/docs/FAQ.html#reducers -share-state.

Pada dasarnya, opsi utama adalah "berikan lebih banyak data dalam tindakan" atau "tulis logika peredam yang tahu untuk mengambil dari A dan meneruskan ke B". Saya benar-benar merasa bahwa pendekatan @neverfox adalah cara terbaik untuk menanganinya jika Anda memilih pendekatan yang berpusat pada peredam.

Saya juga sedang menulis satu set dokumen baru tentang penataan reduksi, di # 1784. Salah satu halaman baru secara khusus membahas konsep ini - "Beyond combineReducers" .

Saya pasti tertarik dengan umpan balik lebih lanjut tentang halaman dokumen draf dan topik ini secara umum.

Apapun yang orang katakan di sini. Saya sangat curiga masalah yang biasanya dihadapi orang hanya ketika mereka merancang negara mereka dengan cara yang tidak ideal. Jika ada banyak ketergantungan di antara cabang-cabang Anda di pohon negara, itu sendiri mungkin merupakan indikasi data duplikat dan dapat menggunakan status 'lebih rendah' ​​/ 'lebih ramping'. Platform React + Redux memberi Anda kekuatan luar biasa di tangan Anda dan pembaruan UI yang sangat deterministik. Mereka yang tidak mengalami masalah pembaruan UI yang aneh dan loop dari pengikatan 2 arah mungkin tidak akan memahami hal ini. Ketika ada banyak kekuatan di tangan Anda secara inheren berarti Anda dapat dengan mudah menembak diri sendiri di kaki juga yang akan menjadi mimpi buruk dan merasakan sakit setiap orang juga. Setelah Anda mendapatkan bagian arsitektur negara bagian dengan benar, segala sesuatu yang lain akan menjadi mudah. Jadi pada dasarnya Anda perlu menghabiskan lebih banyak waktu dalam arsitektur negara tetapi Anda dapat menuai banyak manfaat setelah itu.
Juga saya pikir sangat disarankan untuk menggunakan keadaan ramping, dan perhitungan ulang biasa dan pertama-tama lihat apakah Anda memiliki masalah kinerja. Sangat penting. Hanya gunakan pendekatan pemilihan ulang jika Anda melihat masalah kinerja dari penghitungan ulang (ya, pemilihan ulang dapat diterapkan secara bertahap, bukan @gaearon ?). Saya memang mengambil pendekatan dan memperhatikan bahwa komputasi ulang tidak mempengaruhi sama sekali yang saya khawatirkan pasti akan terjadi tetapi itu tidak pernah terjadi, di sisi lain saya dapat mengurutkan item di setiap penekanan tombol dan aplikasi terasa sangat cepat bahkan tanpa menggunakan pilih ulang. Orang-orang sering kali cenderung berpikir terlalu banyak tentang langkah-langkah penghitungan ulang tetapi tolong jangan pernah lupa prosesor rata-rata saat ini dapat melakukan ratusan dan ribuan macam dalam sepersekian detik. Kisah nyata.

Adakah yang mempertimbangkan pendekatan ini? Apa saja jebakannya?

const combinedReducers = combineReducers({
  dog: dogReducer,
  cat: catReducer
})

const stateInjectedReducers = (state, action) => {
  return {
    ...state,
    frog : frogReducer(state.frog, action, state) // frogReducer depends on cat state
  }
}

export default reduceReducers(combinedReducers, stateInjectedReducers)

Ini murah dan mudah tetapi menarik bagi saya karena itu berarti saya tidak perlu menulis ulang stateInjectedReducers untuk reduksi baru yang perlu disuntikkan dengan status dari tempat lain.

@funwithtriangles : ya, pendekatan yang sangat bagus! Sebenarnya, saya membicarakannya di http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html , dan posting blog saya di http://blog.isquaredsoftware.com/2017/01/praktis-redux -bagian-7-formulir-pengeditan-reduksi / .

@markerikson senang mengetahui saya tidak melakukan sesuatu yang terlalu gila! :) Ironisnya adalah bahwa setelah datang dengan pendekatan itu saya memutuskan bahwa potongan negara dog , cat dan frog saya terlalu terkait untuk menjadi reduksi terpisah dan menggabungkannya menjadi satu ! 😛

Hanya ingin tahu apakah ini praktik yang buruk untuk memiliki beberapa pereduksi yang menghormati tindakan yang sama dan kemudian memicu perubahannya masing-masing di pohon negara mereka sendiri? Saya juga berpikir itu mungkin jika Anda harus melakukan ini, itu mungkin indikator Anda telah menyusun pohon negara Anda secara tidak benar tetapi untuk tujuan katakanlah Anda memiliki dua keadaan yang berbeda

const combinedReducers = combineReducers({
  a: aReducer,
  b: bReducer
})

Dan Anda ingin memicu perubahan status di kedua pohon status dari satu pengiriman. Apakah praktik yang buruk hanya memiliki di kedua reduksi menghormati tindakan yang sama? Tak pelak, pengiriman melewati seluruh combinedReducers dan akhirnya membuat perubahan status di kedua pohon. Satu-satunya masalah adalah Anda harus menggali dan melihat apakah tindakan tertentu memicu perubahan dalam pohon status yang berbeda (tetapi kita dapat menggunakan konvensi penamaan untuk membantu mengatasinya).

@augbog Tidak apa-apa dan merupakan hal yang umum dilakukan di Redux, tetapi tidak benar-benar terkait dengan apa yang dijelaskan di atas. Masalah ini tentang pereduksi yang memiliki akses ke negara bagian dari cabang lain dari pohon negara.

@augbog : itu benar-benar kasus penggunaan yang dimaksudkan untuk Redux! Untuk info lebih lanjut, lihat posting blog saya The Tao of Redux, Bagian 1 - Implementasi dan Maksud .

Sudut lain - Gunakan selektor di akar pohon status Anda untuk mendapatkan data tertentu yang Anda butuhkan dengan memanggil penyeleksi dari sub-cabang negara bagian untuk mendapatkan data yang diperlukan, lalu kembalikan bentuk data yang Anda perlukan:

const selector = state => {
    const x = someSelectorForA(state.a);
    const y = someSelectorForB(state.b);
    return compute(x,y);
}

Pereduksi Anda tetap terenkapsulasi, tidak ada kondisi balapan, dan pemilih melakukan pekerjaan kasar. Dapat menyebabkan beberapa pola rekursif yang bagus.

Setiap kali saya tergoda untuk menjangkau cabang-cabang pohon negara bagian, biasanya pada saat itulah saya harus menggunakan selektor. Saya berusaha sangat keras untuk mengikuti prinsip-prinsip ini:

  1. Pohon negara tidak boleh berisi apa pun yang berasal dari apa pun di pohon negara bagian.
  2. Selektor harus digunakan untuk menghitung data turunan.

Sejauh menyangkut formulir, bagi saya Anda memiliki 2 barang yang termasuk di toko:

  1. Aturan validasi
  2. Data dari input pengguna (teks, blur, peristiwa lain)

Kemudian Anda menggunakan selektor untuk menghitung apakah 2 mengikuti aturan yang ditentukan di 1. Tidak ada alasan untuk menjangkau seluruh pohon status di peredam.

Saya kadang-kadang berpikir, "Bukankah formulir itu valid atau tidak harus dicerminkan dalam status aplikasi? Sepertinya hal utama yang harus diperhatikan." Namun tetap saja melanggar prinsip bahwa negara harus menjadi sumber kebenaran tunggal. Jika ada bug di mana validitas dihitung dengan tidak benar, status Anda akan menjadi tidak konsisten secara internal. Itu seharusnya tidak mungkin.

Selektor adalah tempat yang sangat baik untuk menyimpan data turunan, meskipun hasilnya besar. Gunakan pemilih memo seperti yang disarankan @gaearon , karena status yang dihitung mungkin memiliki banyak pelanggan yang tertarik.

Dan jika peredam Anda membutuhkan informasi kontekstual dari data turunan untuk menafsirkan tindakan dengan benar, Anda dapat memasukkannya ke dalam tindakan saat mengirimkannya.

Alternatif untuk masalah ini, menggunakan redux-thunk. Solusinya adalah dengan menggunakan tindakan perantara, untuk mengaktifkan lebih banyak tindakan sadar konteks berdasarkan keadaan saat ini.

function incrementIfOdd() {
  return (dispatch, getState) => {
    const { counter } = getState();

    if (counter % 2 === 0) {
      return;
    }

    dispatch(increment());
  };
}

Dengan menggunakan solusi ini, tindakan dan pereduksi tetap bodoh, dan logika berpindah ke lapisan perantara yang melakukan pemeriksaan dan pengurangan ekstra berdasarkan interaksi pengguna dan status saat ini.

Ada bagiannya sendiri di dokumen , jadi menurut saya ini cukup menonjol secara pribadi.

Tautan dengan kutipan ini rusak, tetapi saya yakin itu merujuk pada ini: https://redux.js.org/docs/recipes/ComputingDerivedData.html

Namun, pada pandangan kedua, bagian ini sepertinya relevan jika tidak lebih: https://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html

Ya, ke sanalah itu dipindahkan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat