redux-stack - это библиотека, которая помогает создавать модульные, структурированные и более чистые приложения redux, делая первоклассную идею плагинов.

Мы представляем концепцию инициализаторов: небольших фрагментов кода интеграции для каждой библиотеки, которые «декларируют», как она интегрируется. redux-stack объединяет их вместе, чтобы создать собственный конструктор магазинов.

Инициализаторы Redux

инициализатор в redux-stack - это простой модуль Javascript, который экспортирует следующую фигуру:

export default {
  reducers: {}
  enhancers: []
  composers: []
}

Поля Reducer, Enhancer (помните, applyMiddleware - это один из примеров улучшителя) и composers содержат по одному или более стандартных reducer reducer, Enhancer и composer.

Конечно, усилители хранилища - это благословенный способ продвинуться вперед для настройки хранилища, на самом деле я наткнулся на библиотеки, желающие добавить свой собственный редуктор, или предоставить свою собственную функцию createStore, или даже потребовать, чтобы вы использовали их собственную версию функции составления . Это было достаточно распространено, чтобы сделать эти части формы инициализатора.

Давайте создадим инициализатор для ex-navigation: популярной библиотеки навигации для React Native, которая хорошо интегрируется с Redux. Вот необходимая конфигурация, дословно из README:

import { createNavigationEnabledStore, NavigationReducer } from '@exponent/ex-navigation';
import { combineReducers, createStore } from 'redux';

const createStoreWithNavigation = createNavigationEnabledStore({
  createStore,
  navigationStateKey: 'navigation',
});

const store = createStoreWithNavigation(
  /* combineReducers and your normal create store things here! */
  combineReducers({
    navigation: NavigationReducer,
    // other reducers
  })
);

export default store;

Несколько наблюдений: он использует свою собственную функцию createStore, которая называется createStoreWithNavigation. Затем он также хочет внедрить редуктор. Это совершенно тривиально, однако это может стать сложным, если, например, другая библиотека хочет предоставить свою собственную версию createStore. И в более широком смысле - этого действительно достаточно, чтобы спрятать и не беспокоить нас каждый раз, когда мы смотрим на конфигурацию магазина.

Давайте превратим это в инициализатор, совместимый с redux-stack.

Во-первых, проницательный глаз определит, что createStoreWithNavigation можно превратить в стандартный усилитель Redux следующим образом:

const navEnhancer = (createStoreFn) => {
  return createNavigationEnabledStore({
    createStore: createStoreFn,
    navigationStateKey: 'navigation',
  })
}

Теперь, когда у нас есть стандартный усилитель Redux, мы можем сделать инициализатор:

import { NavigationReducer, createNavigationEnabledStore } from '@exponent/ex-navigation'

const navEnhancer = (createStoreFn) => {
  return createNavigationEnabledStore({
    createStore: createStoreFn,
    navigationStateKey: 'navigation',
  })
}

export default {
  name: 'ex-navigation',
  reducers: {navigation: NavigationReducer},
  enhancers: [navEnhancer],
}

И давайте создадим один index.js для объединения всех инициализаторов:

import initNavigation from './ex-navigation'
export default[
  initNavigation
]

После этого мы хотим перетащить инициализатор в удобное место, поэтому мы добавим еще одну папку в стандартный макет Redux:

actions/
components/
constants/
containers/
initializers/   <---
 - ex-navigation.js
 - index.js
reducers/
store/

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

import { createStore, combineReducers } from 'redux'
import { buildStack } from 'redux-stack'
import stack from '@/initializers'

const initialState = {}
const { reducers, enhancer } = buildStack(stack)
const store = createStore(combineReducers(reducers), initialState, enhancer)

export default store

Если вы сохраните чистую структуру, этот блок кода будет одинаковым для каждого приложения, которое вы создаете, и может быть легко извлечен в отдельный фрагмент библиотеки.

Совместное использование инициализаторов

Мы видели, как преобразовать приложение Redux в приложение, использующее redux-stack, что приводит к более чистому установочному коду и более модульному приложению в целом. Это означает, что вместо чтения инструкций по настройке библиотеки и копирования кода в помощник по настройке основного хранилища каждый раз, когда вы добавляете библиотеку, вы создаете инициализатор и помещаете его в папку initializers /.

Создание инициализатора имеет небольшую цену. Однако теперь вы можете легко скопировать этот инициализатор в другой проект, и там он тоже будет работать должным образом. Время потрачено не зря.

Чтобы продвинуть идею на шаг вперед, вы даже можете передать этот инициализатор другой команде или сделать его открытым для других. Если вы являетесь автором библиотеки, вы можете предоставить этот инициализатор для своих пользователей как часть библиотеки.

На пути к инициализаторам и пресетам Redux

Итак, одна из целей redux-stack - иметь обширный заранее созданный набор инициализаторов как часть redux-stack или самих библиотек, чтобы людям не нужно было их создавать. Для каждой известной библиотеки у вас будет готовый инициализатор, который единообразно позаботится о ее настройке и интеграции в ваше приложение Redux.

Другая цель - предоставить пресеты, которые представляют собой готовые наборы инициализаторов. Или, другими словами: полные стеки приложений Redux для использования. Эта идея вдохновлена ​​пресетами babel. Таким образом, вы можете даже сделать что-то вроде приведенного ниже листинга, чтобы получить любимую разновидность инфраструктуры Exponent (примечание: я не связан с Exponent, я просто ценю их библиотеки):

import { createStore, combineReducers } from 'redux'
import { buildStack } from 'redux-stack'
import stack from 'stack-exponent-react-native'

const { reducers, enhancer } = buildStack(stack)
const store = createStore(combineReducers(reducers), {}, enhancer)

export default store

Увидеть общую картину

Вот как Redux Stack улучшает восприятие вашей кодовой базы и поддерживает модульность.

До

import users from '@/modules/users/state'
import settings from '@/modules/settings/state'
import intro from '@/modules/intro/state'
import { NavigationReducer, createNavigationEnabledStore } from '@exponent/ex-navigation'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import { createStore, applyMiddleware, combineReducers, compose } from 'redux'
import { persistStore, autoRehydrate } from 'redux-persist'
import immutableTransform from 'redux-persist-transform-immutable'
import {AsyncStorage} from 'react-native'
const devtools = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

const reducers = {
  users,
  settings,
  intro,
}
const rootReducer = combineReducers({navigation: NavigationReducer, ...reducers})


const logger = createLogger({
  collapsed: true,
  // only log in development mode
  predicate: () => __DEV__,
  // transform immutable state to plain objects
  stateTransformer: state => removeNavigation(fromJS(state).toJS()),
  // transform immutable action payloads to plain objects
  actionTransformer: action =>
    action && action.payload && action.payload.toJS
      ? {...action, payload: action.payload.toJS()}
      : action
})

let middleware = [
  thunk,
  logger
]

const createStoreWithNavigation = createNavigationEnabledStore({
  createStore,
  navigationStateKey: 'navigation',
})

const initialState = {}

const store = createStoreWithNavigation(
  rootReducer,
  initialState,
  compose(
    autoRehydrate(),
    devtools(
      applyMiddleware(...middleware)
    )
  )
)

persistStore(store, {
  storage: AsyncStorage,
  blacklist: ['navigation'],
  transforms: [immutableTransform()]
})

export default store

Этот установочный код запутан. Кроме того, импорт и использование библиотеки разделены и не имеют смысла - они повсюду. Это также означает, что если вы когда-нибудь захотите изменить или удалить компоненты, вам придется искать их и надеяться, что вы не сломали свое приложение; мы также видим, что очень важно то, как вы упорядочиваете свою интеграцию.

После

import { createStore, combineReducers } from 'redux'
import { buildStack } from 'redux-stack'
import stack from '@/initializers'

const initialState = {}
const { reducers, enhancer } = buildStack(stack)
const store = createStore(combineReducers(reducers), initialState, enhancer)

export default store

И у нас есть новая папка инициализаторов:

initializers/
├── devtools.js
├── ex-navigation.js
├── index.js
├── middleware.js
├── reducers.js
├── redux-logger.js
└── redux-persist.js

Код настройки - это простая задача. Это чисто и разумно и, вероятно, будет выглядеть одинаково для каждого нового приложения, которое вы создаете, поэтому вы даже можете извлечь его в модуль npm.

Область инициализаторов раздельная и модульная. Вы можете удалить и добавить любой инициализатор, который вам нравится, и быть уверенным, что ничего не сломается. Если вы построили свое приложение по модульному принципу, каждый модуль может внести инициализатор, который сам настраивается, и никакой драмы не произойдет - никакой другой код не должен изменяться, и ни один модуль не может сломаться по ошибке.

Начиная

Вы можете начать использовать redux-stack сегодня, реализация которого составляет не более 30 строк кода, но идея о том, что унифицированные инициализаторы являются первоклассными, гораздо глубже. Идите дальше и ознакомьтесь с репозиторием Github для получения дополнительной информации об использовании redux-stack.

Спасибо Maiz Lulkin и Xavier Via за то, что прислушались к моим мыслям!