Bagaimana cara memuat reduksi secara dinamis untuk pemecahan kode dalam aplikasi Redux?

189

Saya akan bermigrasi ke Redux.

Aplikasi saya terdiri dari banyak bagian (halaman, komponen) jadi saya ingin membuat banyak reduksi. Contoh redux menunjukkan bahwa saya harus menggunakan combineReducers()untuk menghasilkan satu peredam.

Juga saya mengerti aplikasi Redux harus memiliki satu toko dan dibuat setelah aplikasi dimulai. Ketika toko sedang dibuat saya harus melewati peredam gabungan saya. Ini masuk akal jika aplikasinya tidak terlalu besar.

Tetapi bagaimana jika saya membangun lebih dari satu bundel JavaScript? Misalnya, setiap halaman aplikasi memiliki bundel sendiri. Saya pikir dalam hal ini peredam kombinasi tidak bagus. Saya melihat melalui sumber-sumber Redux dan saya telah menemukan replaceReducer()fungsinya. Sepertinya itulah yang saya inginkan.

Saya dapat membuat reducer gabungan untuk setiap bagian aplikasi saya dan digunakan replaceReducer()ketika saya berpindah di antara bagian aplikasi.

Apakah ini pendekatan yang baik?

Pavel Teterin
sumber

Jawaban:

245

Perbarui: lihat juga bagaimana Twitter melakukannya .

Ini bukan jawaban lengkap tetapi harus membantu Anda memulai. Perhatikan bahwa saya tidak membuang reduksi lama — saya hanya menambahkan yang baru ke daftar kombinasi. Saya tidak melihat alasan untuk membuang reducers lama — bahkan di aplikasi terbesar Anda tidak mungkin memiliki ribuan modul dinamis, yang merupakan titik di mana Anda mungkin ingin memutuskan beberapa reducers dalam aplikasi Anda.

reducers.js

import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';

export default function createReducer(asyncReducers) {
  return combineReducers({
    users,
    posts,
    ...asyncReducers
  });
}

store.js

import { createStore } from 'redux';
import createReducer from './reducers';

export default function configureStore(initialState) {
  const store = createStore(createReducer(), initialState);
  store.asyncReducers = {};
  return store;
}

export function injectAsyncReducer(store, name, asyncReducer) {
  store.asyncReducers[name] = asyncReducer;
  store.replaceReducer(createReducer(store.asyncReducers));
}

route.js

import { injectAsyncReducer } from './store';

// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).

function createRoutes(store) {
  // ...

  const CommentsRoute = {
    // ...

    getComponents(location, callback) {
      require.ensure([
        './pages/Comments',
        './reducers/comments'
      ], function (require) {
        const Comments = require('./pages/Comments').default;
        const commentsReducer = require('./reducers/comments').default;

        injectAsyncReducer(store, 'comments', commentsReducer);
        callback(null, Comments);
      })
    }
  };

  // ...
}

Mungkin ada cara yang lebih rapi untuk mengungkapkan ini — saya hanya menunjukkan idenya.

Dan Abramov
sumber
13
Saya ingin melihat fungsionalitas jenis ini ditambahkan ke proyek. Kemampuan untuk menambahkan reducers secara dinamis adalah suatu keharusan ketika berhadapan dengan pemecahan kode dan aplikasi besar. Saya memiliki seluruh sub pohon yang mungkin tidak dapat diakses oleh beberapa pengguna dan memuat semua reduksi adalah pemborosan. Bahkan dengan redux-abaikan aplikasi besar benar-benar dapat menumpuk reduksi.
JeffBaumgardt
2
Terkadang, pemborosan yang lebih besar untuk 'mengoptimalkan' sesuatu yang tidak penting.
XML
1
Semoga komentar di atas masuk akal ... ketika saya kehabisan kamar. Tetapi pada dasarnya saya tidak melihat cara mudah untuk memiliki reduksi dikombinasikan ketika menjadi satu cabang di pohon negara kami ketika mereka dimuat secara dinamis dari rute yang berbeda /homepagedan kemudian lebih banyak cabang yang dimuat ketika pengguna pergi ke mereka profile.. Contoh bagaimana untuk melakukan ini, akan luar biasa. Kalau tidak, saya akan kesulitan meratakan pohon negara saya atau saya harus memiliki nama cabang yang sangat spesifik user-permissionsdanuser-personal
BryceHayden
1
Dan bagaimana saya harus bertindak, jika saya memiliki keadaan awal?
Stalso
3
github.com/mxstbr/react-boilerplate boilerplate menggunakan teknik yang sama persis yang disebutkan di sini untuk memuat reduksi.
Pouya Sanooei
25

Ini adalah bagaimana saya mengimplementasikannya di aplikasi saat ini (berdasarkan kode oleh Dan dari masalah GitHub!)

// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
  constructor(initialReducers = {}) {
    this._reducers = {...initialReducers}
    this._emitChange = null
  }
  register(newReducers) {
    this._reducers = {...this._reducers, ...newReducers}
    if (this._emitChange != null) {
      this._emitChange(this.getReducers())
    }
  }
  getReducers() {
    return {...this._reducers}
  }
  setChangeListener(listener) {
    if (this._emitChange != null) {
      throw new Error('Can only set the listener for a ReducerRegistry once.')
    }
    this._emitChange = listener
  }
}

Buat instance registri saat mem-bootstrap aplikasi Anda, dengan memasukkan reduksi yang akan disertakan dalam bundel entri:

// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)

Kemudian ketika mengonfigurasi toko dan rute, gunakan fungsi yang Anda bisa memberikan registri peredam ke:

var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)

Di mana fungsi-fungsi ini terlihat seperti:

function createRoutes(reducerRegistry) {
  return <Route path="/" component={App}>
    <Route path="core" component={Core}/>
    <Route path="async" getComponent={(location, cb) => {
      require.ensure([], require => {
        reducerRegistry.register({async: require('./reducers/async')})
        cb(null, require('./screens/Async'))
      })
    }}/>
  </Route>
}

function createStore(reducerRegistry) {
  var rootReducer = createReducer(reducerRegistry.getReducers())
  var store = createStore(rootReducer)

  reducerRegistry.setChangeListener((reducers) => {
    store.replaceReducer(createReducer(reducers))
  })

  return store
}

Berikut adalah contoh langsung dasar yang dibuat dengan pengaturan ini, dan sumbernya:

Ini juga mencakup konfigurasi yang diperlukan untuk mengaktifkan pemuatan ulang panas untuk semua reduksi Anda.

Jonny Buchanan
sumber
Terima kasih @jonny, hanya kepala, contohnya adalah melemparkan kesalahan sekarang.
Jason J. Nathan
deklarasi createReducer () hilang dari jawaban Anda (saya tahu itu ada dalam jawaban Dan Abrahamov tapi saya pikir termasuk itu akan menghindari kebingungan)
Packet Tracer
6

Sekarang ada modul yang menambahkan injeksi reduksi ke toko redux. Itu disebut Redux Injector .

Berikut cara menggunakannya:

  1. Jangan gabungkan reduksi. Alih-alih menempatkan mereka dalam objek fungsi (bersarang) seperti biasa tetapi tanpa menggabungkannya.

  2. Gunakan createInjectStore dari redux-injector daripada createStore dari redux.

  3. Suntikkan reduksi baru dengan injectReducer.

Berikut ini sebuah contoh:

import { createInjectStore, injectReducer } from 'redux-injector';

const reducersObject = {
   router: routerReducerFunction,
   data: {
     user: userReducerFunction,
     auth: {
       loggedIn: loggedInReducerFunction,
       loggedOut: loggedOutReducerFunction
     },
     info: infoReducerFunction
   }
 };

const initialState = {};

let store = createInjectStore(
  reducersObject,
  initialState
);

// Now you can inject reducers anywhere in the tree.
injectReducer('data.form', formReducerFunction);

Pengungkapan Penuh: Saya adalah pencipta modul.

Randall Knutson
sumber
4

Per Oktober 2017:

  • Reedux

    mengimplementasikan apa yang disarankan Dan dan tidak lebih, tanpa menyentuh toko Anda, proyek Anda atau kebiasaan Anda

Ada perpustakaan lain juga, tetapi mereka mungkin memiliki terlalu banyak dependensi, contoh kurang, penggunaan rumit, tidak kompatibel dengan beberapa middlewares atau mengharuskan Anda untuk menulis ulang manajemen negara Anda. Disalin dari halaman intro Reedux:

Silviu-Marian
sumber
2

Kami merilis perpustakaan baru yang membantu memodulasi aplikasi Redux dan memungkinkan menambah / menghapus Reducers dan middlewares secara dinamis.

Silakan lihat di https://github.com/Microsoft/redux-dynamic-modules

Modul memberikan manfaat berikut:

  • Modul dapat dengan mudah digunakan kembali di seluruh aplikasi, atau di antara beberapa aplikasi serupa.

  • Komponen mendeklarasikan modul yang dibutuhkan oleh mereka dan modul redux-dynamic-memastikan bahwa modul dimuat untuk komponen.

  • Modul dapat ditambahkan / dihapus dari toko secara dinamis, mis. ketika komponen mount atau ketika pengguna melakukan suatu tindakan

fitur

  • Kelompokkan reduksi, middleware, dan status bersama menjadi satu modul yang dapat digunakan kembali.
  • Tambah dan hapus modul dari toko Redux kapan saja.
  • Gunakan komponen yang disertakan untuk secara otomatis menambahkan modul ketika komponen diberikan
  • Ekstensi menyediakan integrasi dengan perpustakaan populer, termasuk redux-saga dan redux-observable

Contoh Skenario

  • Anda tidak ingin memuat kode untuk semua reduksi Anda di muka. Tentukan modul untuk beberapa reduksi dan gunakan DynamicModuleLoader dan perpustakaan seperti react-loadable untuk mengunduh dan menambahkan modul Anda saat runtime.
  • Anda memiliki beberapa reducers / middleware umum yang perlu digunakan kembali di berbagai area aplikasi Anda. Tentukan modul dan mudah memasukkannya ke dalam area tersebut
  • Anda memiliki mono-repo yang berisi beberapa aplikasi yang memiliki status serupa. Buat paket yang berisi beberapa modul dan gunakan kembali di aplikasi Anda
Kode Ninja
sumber
0

Berikut adalah contoh lain dengan toko pemecahan kode dan redux, cukup sederhana & elegan menurut saya. Saya pikir ini mungkin sangat berguna bagi mereka yang mencari solusi yang berfungsi.

Ini toko ini sedikit disederhanakan ia tidak memaksa Anda untuk memiliki namespace (reducer.name) di objek negara Anda, tentu saja mungkin ada tabrakan dengan nama tetapi Anda dapat mengontrol ini dengan menciptakan konvensi penamaan untuk pengecil Anda dan itu harus baik-baik saja.

Maksym Oliinyk
sumber