Як динамічно завантажувати редуктори для розбиття коду в додатку Redux?


189

Я переїжджаю до Редукса.

Моя програма складається з безлічі частин (сторінок, компонентів), тому я хочу створити багато редукторів. Приклади Redux показують, що я повинен використовувати combineReducers()для створення одного редуктора.

Крім того, як я розумію, програма Redux повинна мати один магазин, і вона створюється після запуску програми. Під час створення магазину я повинен здати свій комбінований редуктор. Це має сенс, якщо додаток не надто великий.

Але що робити, якщо я будую кілька пакетів JavaScript? Наприклад, кожна сторінка програми має власний пакет. Я думаю, що в цьому випадку один комбінований редуктор не є гарним. Я переглянув джерела Redux і знайшов replaceReducer()функцію. Здається, що я хочу.

Я можу створити комбінований редуктор для кожної частини моєї програми та використовувати її replaceReducer()при переміщенні між частинами програми.

Це хороший підхід?

Відповіді:


245

Оновлення: дивіться також, як це робить Twitter .

Це не є повною відповіддю, але має допомогти вам розпочати роботу. Зауважте, що я не викидаю старі редуктори - я просто додаю нові до списку комбінацій. Я не бачу причин , щоб викинути старі восстановителей навіть в найбільшому додаток , яке ви навряд чи матимуть тисячі динамічних модулів, яка є точкою , де ви , можливо , хочете відключити деякі восстановителей в додатку.

redurs.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);
      })
    }
  };

  // ...
}

Це може бути акуратніше спосіб висловити це - я просто показую ідею.


13
Я хотів би, щоб цей тип функціональності додався до проекту. Можливість динамічно додавати редуктори необхідна при роботі з розбиттям коду та великими програмами. У мене є цілі під дерева, до яких деякі користувачі можуть не отримати доступ, а завантаження всіх редукторів - це марно. Навіть з великими програмами redux-ignore можна дійсно скласти редуктори.
JeffBaumgardt

2
Іноді більша трата «оптимізувати» щось несуттєве.
XML

1
Сподіваємось, вищезгаданий коментар має сенс ... коли я вибігав із кімнати. Але в основному я не бачу простий спосіб об'єднати редуктори в одну гілку на нашому дереві штатів, коли вони динамічно завантажуються з різних маршрутів, /homepageа потім більше цієї гілки завантажується, коли користувач переходить до свого profile.прикладу. зробити це було б дивним. Інакше мені важко вирівняти своє державне дерево або мені доведеться мати дуже конкретні назви гілок user-permissionsіuser-personal
BryceHayden

1
І як мені діяти, якщо у мене початковий стан?
Сталсо

3
github.com/mxstbr/react-boilerplate котловадна плита використовує точно таку ж техніку, згадану тут, для завантаження редукторів.
Pouya Sanooei

25

Ось як я реалізував це в поточному додатку (на основі коду від Dan з випуску 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
  }
}

Створіть примірник реєстру під час завантаження програми, передавши редуктори, які будуть включені до вхідного пакету:

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

Потім, конфігуруючи магазин і маршрути, використовуйте функцію, якій ви можете надати реєстр редукторів:

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

Де ці функції виглядають приблизно так:

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
}

Ось основний живий приклад, створений за допомогою цього налаштування, та його джерело:

Він також охоплює необхідну конфігурацію, щоб увімкнути гаряче перезавантаження для всіх ваших редукторів.


Дякуємо @jonny, просто головою вгору, приклад зараз є помилкою.
Джейсон Дж. Натан

у вашій відповіді відсутня декларація createReducer () (я знаю, що це є у відповіді Дана Абрахамова, але я думаю, включення цього дозволить уникнути плутанини)
Packet Tracer

6

Зараз існує модуль, який додає інжекторні редуктори до сховища редукції. Він називається інжектор Redux .

Ось як його використовувати:

  1. Не комбінуйте редуктори. Замість цього помістіть їх у (вкладений) об'єкт функцій, як зазвичай, але без їх комбінування.

  2. Використовуйте createInjectStore з redux-injector замість createStore з redux.

  3. Введіть нові редуктори за допомогою injectReducer.

Ось приклад:

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);

Повне розкриття інформації: я творець модуля.


4

Станом на жовтень 2017 року:

  • Reedux

    реалізує те, що запропонував Ден, і більше нічого, не торкаючись магазину, вашого проекту чи звичок

Є й інші бібліотеки, але вони можуть мати занадто багато залежностей, менше прикладів, складне використання, несумісні з деякими середніми програмами або вимагають від вас переписати управління державою. Скопійовано із вступної сторінки Reedux:


2

Ми випустили нову бібліотеку, яка допомагає модулювати додаток Redux і дозволяє динамічно додавати / видаляти редуктори та середні продукти.

Погляньте на https://github.com/Microsoft/redux-dynamic-modules

Модулі забезпечують такі переваги:

  • Модулі можна легко повторно використовувати в додатку або між кількома подібними програмами.

  • Компоненти оголошують потрібні їм модулі, а редуксно-динамічні модулі забезпечують завантаження модуля для компонента.

  • Модулі можна додавати / видаляти з магазину динамічно, напр. коли компонент монтується або коли користувач виконує дію

Особливості

  • Згрупуйте разом редуктори, програмне забезпечення та стан в єдиний модуль, який можна повторно використовувати.
  • Додайте та видаліть модулі з магазину Redux будь-коли.
  • Використовуйте включений компонент для автоматичного додавання модуля, коли компонент надається
  • Розширення забезпечують інтеграцію з популярними бібліотеками, включаючи redux-saga та redux-opazili

Приклад сценаріїв

  • Ви не хочете завантажувати код на всі ваші редуктори вперед. Визначте модуль для деяких редукторів і використовуйте DynamicModuleLoader та бібліотеку, подібну до реакції, для завантаження та додавання свого модуля під час виконання.
  • У вас є кілька поширених редукторів / проміжних програм, які потрібно повторно використовувати в різних областях вашої програми. Визначте модуль і легко включіть його в ці області.
  • У вас є монорепо, що містить кілька додатків, які мають подібний стан. Створіть пакет, що містить деякі модулі, і повторно використовуйте їх у своїх програмах

0

Ось ще один приклад із розбиттям коду та резервними магазинами, на мою думку, досить простим та елегантним. Я думаю, що це може бути досить корисним для тих, хто шукає робочого рішення.

Цей магазин трохи спрощений, він не змушує вас мати об'єкт імен (reducer.name) у вашому об'єкті стану, звичайно, може статися зіткнення з іменами, але ви можете керувати цим, створивши конвенцію про іменування для ваших редукторів і це має бути добре.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.