├── .gitignore
├── index.js
├── .babelrc
├── src
├── index.js
└── libs
│ ├── settings.js
│ ├── provider.js
│ ├── common.js
│ ├── store.js
│ ├── connectSagas.js
│ └── helper.js
├── index.html
├── .eslintrc
├── package.json
├── index.d.ts
├── webpackPlugin
└── index.js
├── README.md
└── dist
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .idea/
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | if (process.env.NODE_ENV === 'production') {
4 | module.exports = require('./dist/index.js')
5 | } else {
6 | module.exports = require('./dist/index.development.js')
7 | }
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"],
3 | "plugins": [
4 | "@babel/plugin-transform-runtime",
5 | "@babel/plugin-syntax-dynamic-import",
6 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
7 | ["@babel/plugin-proposal-class-properties", { "loose": true }]
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { buildRedux, buildListRedux } from './libs/helper'
2 | import config from './libs/settings'
3 | import connectSagas, {sagas, reducers} from './libs/connectSagas'
4 | import { myConnect, history } from './libs/store'
5 | import Provider, { createStore } from './libs/provider'
6 |
7 | export default {
8 | buildRedux: connectSagas(buildRedux),
9 | connect: myConnect,
10 | Provider,
11 | config,
12 | createStore,
13 | history,
14 | sagas,
15 | reducers,
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React Redux Creator
10 |
11 |
12 |
13 |
14 | React Redux Creator
15 |
16 | here is detail
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/libs/settings.js:
--------------------------------------------------------------------------------
1 | // 初始化配置
2 |
3 | export let defaultOptions = {
4 | logger: true, // redux-logger
5 | catchError: true, // redux error console
6 | fetchMethod: null, // fetch请求
7 | history: 'browser', // browser, hash, memory, none
8 | autoActions: true,
9 | middleware: [], // middleware
10 | store: null,
11 | }
12 |
13 | const settings = () => {
14 | let options = defaultOptions
15 | return (opts) => {
16 | if (opts) {
17 | options = {
18 | ...options,
19 | ...opts
20 | }
21 | }
22 | return options
23 | }
24 | }
25 |
26 | const config = settings()
27 | export default config
28 |
--------------------------------------------------------------------------------
/src/libs/provider.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { Provider } from 'react-redux'
3 | import { ConnectedRouter } from 'connected-react-router'
4 | import { sagas, reducers } from './connectSagas'
5 | import configureStore, { history } from './store'
6 | import config from './settings'
7 |
8 | export const createStore = (initState = {}) => {
9 | const store = configureStore(initState, reducers, sagas)
10 | config({ store })
11 | return store
12 | }
13 |
14 | export default ({ routes, initState = {} }) => {
15 | const store = createStore(initState)
16 | return (
17 |
18 |
19 | {typeof routes === 'function' ? routes() : routes}
20 |
21 |
22 | )
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/src/libs/common.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
3 | /**
4 | * 将对象转变为 params string
5 | * e.g. { name: 'user', age: 13} => name=user&age=13
6 | * @param obj
7 | * @return {*}
8 | */
9 | export function obj2params(obj, prefix = '', suffix = '') {
10 | if (typeof obj !== 'object' || !obj) return ''
11 |
12 | let params = []
13 | Object.keys(obj).forEach(key => {
14 | if (obj[key] !== undefined && obj[key] !== null) {
15 | if (obj[key] instanceof Object) { // 数组和对象特殊处理
16 | params.push(`${key}=${JSON.stringify(obj[key])}`)
17 | } else {
18 | params.push(`${key}=${obj[key]}`)
19 | }
20 | }
21 | })
22 | return prefix + params.join('&') + suffix
23 | }
24 |
25 | /**
26 | * 下划线转驼峰
27 | * @param name
28 | */
29 | export const underScoreToCamel = (name) =>
30 | name
31 | .split('_')
32 | .map((item, index) => {
33 | if (item.length > 0) {
34 | if (index === 0) {
35 | return item
36 | } else {
37 | return item[0].toUpperCase() + item.substring(1).toLowerCase()
38 | }
39 | } else {
40 | return ''
41 | }
42 | })
43 | .join('')
44 |
45 | export const notEmpty = value => value !== null && value !== undefined && value !== ''
46 | export const notNullOrUndefiend = value => value !== null && value !== undefined
47 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "es6": true,
5 | "node": true,
6 | "browser": true,
7 | "commonjs": true
8 | },
9 | "parserOptions": {
10 | "ecmaVersion": 6,
11 | "sourceType": "module",
12 | "ecmaFeatures": {
13 | "experimentalObjectRestSpread": true,
14 | "legacyDecorators": true,
15 | "jsx": true
16 | }
17 | },
18 | "plugins": ["react", "prettier"],
19 | "rules": {
20 | "react/prop-types": 0,
21 | "react/display-name": [0],
22 | "react/jsx-uses-react": "error",
23 | "react/jsx-uses-vars": "error",
24 | "no-console": "off",
25 | // "prettier/prettier": "error",
26 | // "indent": ["error", 2],
27 | // "no-mixed-spaces-and-tabs": "error",
28 | // "camelcase": "error",
29 | // "eqeqeq": "warn",
30 | // "curly": "error",
31 | // "no-undef": "error",
32 | "no-unused-vars": "off",
33 | // "max-params": "warn",
34 | "linebreak-style": [0], // ["error", "unix"],
35 | "quotes": ["error", "single"],
36 | "no-multiple-empty-lines": [2, { "max": 2 }],
37 | "semi": ["error", "never"], //["error", "always"]
38 | "import/no-cycle": "off",
39 | "import/no-mutable-exports": "off"
40 | },
41 | "extends": [
42 | "airbnb-base",
43 | "eslint:recommended",
44 | "plugin:react/recommended"
45 | // "plugin:prettier/recommended"
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/src/libs/store.js:
--------------------------------------------------------------------------------
1 | import { createBrowserHistory, createHashHistory, createMemoryHistory } from 'history'
2 | import { applyMiddleware, compose, createStore, combineReducers, bindActionCreators } from 'redux'
3 | import { routerMiddleware, connectRouter } from 'connected-react-router'
4 | import createSagaMiddleware from 'redux-saga'
5 | import { all } from 'redux-saga/effects'
6 | import { connect } from 'react-redux'
7 | import logger from 'redux-logger'
8 | import config from './settings'
9 |
10 |
11 | const getHistory = () => {
12 | const options = config()
13 | let historyStore
14 | switch (options.history) {
15 | case 'browser':
16 | historyStore = createBrowserHistory()
17 | break
18 | case 'hash':
19 | historyStore = createHashHistory()
20 | break
21 | case 'memory':
22 | historyStore = createMemoryHistory()
23 | break
24 | default:
25 | historyStore = null
26 | }
27 | return historyStore
28 | }
29 |
30 | export const history = getHistory()
31 |
32 | const sagaMiddleware = createSagaMiddleware()
33 |
34 | const createRootReducer =
35 | (history, reducers) => combineReducers({
36 | ...(history ? { router: connectRouter(history) } : {}),
37 | ...reducers,
38 | })
39 |
40 |
41 | const combineMiddleware = () => {
42 | const options = config()
43 | const history = getHistory()
44 |
45 | let middleWare = [
46 | sagaMiddleware,
47 | ]
48 | if (history) {
49 | middleWare.push(routerMiddleware(history)) // for dispatching history actions
50 | }
51 |
52 | if (Object.prototype.toString.call(options.middleware) === '[object Array]') {
53 | middleWare = [...middleWare, ...options.middleware]
54 | }
55 |
56 | console.log('options logger', options.logger)
57 | if (options.logger) {
58 | middleWare.push(logger)
59 | }
60 | return compose(
61 | applyMiddleware(...middleWare),
62 | )
63 | }
64 |
65 | export default function configureStore(initState, reducers, sagas) {
66 | const store = createStore(
67 | createRootReducer(history, reducers),
68 | initState,
69 | combineMiddleware(),
70 | )
71 | sagaMiddleware.run(function* rootSaga(getState) {
72 | yield all(sagas)
73 | })
74 |
75 | return store
76 | }
77 |
78 | export const myConnect = (mapState, mapPropsObject) => connect(
79 | mapState,
80 | dispatch => bindActionCreators(
81 | {
82 | ...mapPropsObject,
83 | },
84 | dispatch,
85 | ),
86 | )
87 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-creator",
3 | "version": "1.0.0",
4 | "description": "redux helper to create redux actions & reducers with Immutable(seamless-immutable)",
5 | "main": "index.js",
6 | "typings": "index.d.ts",
7 | "files": [
8 | "dist",
9 | "webpackPlugin",
10 | "index.d.ts"
11 | ],
12 | "scripts": {
13 | "test": "echo \"Error: no test specified\" && exit 1",
14 | "build": "webpack --config ./build/webpack.build.js",
15 | "buildDev": "webpack --config ./build/webpack.build.development.js",
16 | "lint": "./node_modules/.bin/eslint src"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/joyerz/react-redux-creator.git"
21 | },
22 | "keywords": [
23 | "react-redux-creator",
24 | "creator",
25 | "create",
26 | "redux",
27 | "helper"
28 | ],
29 | "author": "joyer zhong",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/joyerz/react-redux-creator/issues"
33 | },
34 | "homepage": "https://github.com/joyerz/react-redux-creator#readme",
35 | "dependencies": {
36 | "connected-react-router": "^6.5.2",
37 | "chokidar": "^2.1.8",
38 | "history": "^4.10.1",
39 | "react": ">=16.9.0",
40 | "react-dom": ">=16.9.0",
41 | "react-redux": "^7.1.1",
42 | "redux": "^4.0.4",
43 | "redux-actions": "^2.6.5",
44 | "redux-logger": "^3.0.6",
45 | "redux-saga": "^1.0.5",
46 | "seamless-immutable": "^7.1.4"
47 | },
48 | "devDependencies": {
49 | "@babel/cli": "^7.14.5",
50 | "@babel/core": "^7.4.3",
51 | "@babel/plugin-proposal-class-properties": "^7.5.5",
52 | "@babel/plugin-proposal-decorators": "^7.6.0",
53 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
54 | "@babel/plugin-transform-runtime": "^7.4.3",
55 | "@babel/polyfill": "^7.6.0",
56 | "@babel/preset-env": "^7.4.3",
57 | "@babel/preset-react": "^7.0.0",
58 | "@babel/runtime": "^7.5.5",
59 | "axios": "^0.21.1",
60 | "babel-eslint": "^10.0.3",
61 | "babel-loader": "^8.0.6",
62 | "clean-webpack-plugin": "^2.0.1",
63 | "eslint": "^5.16.0",
64 | "eslint-config-airbnb-base": "^13.1.0",
65 | "eslint-config-prettier": "^4.2.0",
66 | "eslint-import-resolver-alias": "^1.1.2",
67 | "eslint-loader": "^3.0.0",
68 | "eslint-plugin-import": "^2.17.2",
69 | "eslint-plugin-prettier": "^3.0.1",
70 | "eslint-plugin-react": "^7.12.4",
71 | "html-webpack-plugin": "^3.2.0",
72 | "react-router": "^5.2.0",
73 | "terser-webpack-plugin": "^1.4.1",
74 | "webpack": "^4.30.0",
75 | "webpack-cli": "^3.3.1",
76 | "webpack-dev-server": "^3.11.2"
77 | }
78 | }
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | type ProviderTProps = {
4 | routes: any
5 | }
6 | declare class ProviderT extends React.Component{
7 | new (Props: ProviderTProps): {};
8 | }
9 |
10 | interface Actions {
11 | start: (params?: object) => any,
12 | success: (data?: object) => any,
13 | error: (errorMessage: string) => any,
14 | reset: () => void
15 | }
16 |
17 | interface FnConfig {
18 | put: Function,
19 | call: Function,
20 | getAction: (actionName: string) => Actions,
21 | getState: (actionName: string) => any
22 | }
23 |
24 | type PreFunction = (payload: any, options: FnConfig) => string | object
25 | type DataFunction = (payload: any, options: FnConfig) => object
26 | type HandleFunction = (data: any, payload: any, options: FnConfig) => any
27 | type HandleListFunction = (data: any, payload: any, options: FnConfig) => any
28 | type HandleErrorFunction = (err: any, payload: any, options: FnConfig) => any
29 |
30 | type SagaConfig = {
31 | url?: string | PreFunction,
32 | method?: string,
33 | data?: object | DataFunction,
34 | headers?: object,
35 | extract?: object,
36 | fetch?: FetchFunction,
37 | onAfter?: HandleFunction | any,
38 | onResult?: HandleFunction,
39 | onError?: HandleErrorFunction,
40 | }
41 |
42 | interface SagaConfiguration {
43 | url?: string | PreFunction,
44 | method?: string,
45 | data?: object | DataFunction,
46 | headers?: object,
47 | extract?: object,
48 | fetch?: FetchFunction,
49 | onAfter?: (data: any, payload: any, options: FnConfig) => any
50 | onResult?: (data: any, payload: any, options: FnConfig) => any
51 | onError?: (err: any, payload: any, options: FnConfig) => any
52 | }
53 |
54 | type BuildRedux = (actionName: string, defaultData?: object) =>
55 | (config: SagaConfiguration) => Actions
56 |
57 | type ConnectReturn = (Component: any) => any
58 | type Connect = (mapStateToProps: Function, actionsToProps: object) => ConnectReturn
59 |
60 | interface FetchConfig {
61 | url?: string,
62 | method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTION',
63 | headers?: object,
64 | data?: object,
65 | }
66 | type FetchFunction = (fetch: FetchConfig) => Promise
67 | interface Settings {
68 | fetchMethod?: FetchFunction,
69 | logger?: boolean,
70 | history?: 'browser' | 'hash' | 'memory',
71 | middleware?: Array,
72 | autoActions?: boolean,
73 | }
74 | type Config = (settings: Settings) => void
75 |
76 | // 定义fetch方法
77 | export const config: Config
78 |
79 | // 定义buildRedux
80 | export const buildRedux: BuildRedux
81 |
82 | // 定义connect
83 | export const connect: Connect
84 |
85 | // 定义provider
86 | export const Provider: typeof ProviderT
87 |
88 | // history
89 | export const history: any
90 |
91 | // 导出createStore,比如在taro中无法使用Provider
92 | export const createStore: (initState?: object) => object
93 |
--------------------------------------------------------------------------------
/webpackPlugin/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const chokidar = require('chokidar')
4 |
5 | let watching = false
6 | const cache = {
7 | importRedux: null,
8 | combineRedux: null,
9 | importSaga: null,
10 | combineSaga: null,
11 | }
12 |
13 | function reduxSagaPlguin(folder, connectFile) {
14 | this.folder = folder
15 | this.connectFile = connectFile
16 | console.log('folder', folder)
17 | }
18 |
19 | reduxSagaPlguin.prototype.apply = function (compiler) {
20 | const hooks = compiler.hooks
21 | // 指定一个挂载到 webpack 自身的事件钩子。
22 | if(hooks) {
23 | hooks.compile.tap('reduxSagaPlguin', () => {
24 | console.log('plugin stack')
25 | addWatcher(this.folder, this.connectFile)
26 | })
27 | } else {
28 | compiler.plugin('emit', function (
29 | compilation, /* 处理 webpack 内部实例的特定数据。*/
30 | callback,
31 | ) {
32 | addWatcher(this.folder, this.connectFile)
33 | // 功能完成后调用 webpack 提供的回调。
34 | callback()
35 | })
36 | }
37 | }
38 |
39 | reduxSagaPlguin.prototype.convert = convertingFiles
40 |
41 | function addWatcher(folder, connectFile) {
42 | console.log('add Watcher')
43 | convertingFiles(folder, connectFile)
44 | if (!watching) {
45 | chokidar
46 | .watch(folder, {
47 | ignored: /\.(jsx|scss|css)$/,
48 | })
49 | .on('all', (event, path) => {
50 | convertingFiles(folder, connectFile)
51 | })
52 | watching = true
53 | }
54 | }
55 |
56 | function readDir(folder) {
57 | const receivedFiles = []
58 | const files = fs.readdirSync(folder)
59 | files.forEach(function (filename) {
60 | const filePath = path.join(folder, filename)
61 | const stats = fs.statSync(filePath)
62 | if (stats.isDirectory()) {
63 | const f = readDir(filePath)
64 | receivedFiles.push(...f)
65 | } else {
66 | receivedFiles.push(filePath)
67 | }
68 | })
69 | return receivedFiles
70 | }
71 |
72 | function convertingFiles(folder, connectFile) {
73 | const files = readDir(folder)
74 | const reduxFiles = files.filter((file) => /redux\.ts$/.test(file))
75 | console.log('folder')
76 | console.log(folder, reduxFiles, end)
77 | convertRedux(reduxFiles, connectFile)
78 | }
79 |
80 | function convertRedux(files, connectFile) {
81 | console.log('redux files', files)
82 | let importRedux = ''
83 |
84 | // handle redux files
85 | files.forEach((file) => {
86 | const p = file.replace(folder, 'pages').replace('.js', '')
87 |
88 | importRedux += `import '${p}'\n`
89 | })
90 |
91 | if (cache.importRedux !== importRedux) {
92 | const replaceTo =
93 | '// {{__IMPORT_REDUX_START__}}\r\n' +
94 | importRedux +
95 | '\r\n // {{__IMPORT_REDUX_END}}'
96 | const reg = /\/\/ {{__IMPORT_REDUX_START__}}([\w\W]*)?\/\/ {{__IMPORT_REDUX_END__}}/gi
97 |
98 | let string = fs.readFileSync(connectFile, 'utf-8')
99 | string = string.replace(reg, replaceTo)
100 | fs.writeFileSync(connectFile, string, 'utf8')
101 | console.log('combine redux files...'.rainbow)
102 | cache.importRedux = importRedux
103 | }
104 | }
105 |
106 | module.exports = reduxSagaPlguin
107 |
--------------------------------------------------------------------------------
/src/libs/connectSagas.js:
--------------------------------------------------------------------------------
1 | import { put, call, takeLatest, select } from 'redux-saga/effects'
2 | import { obj2params, underScoreToCamel, notNullOrUndefiend } from './common'
3 | import config from './settings'
4 |
5 | // 常规sagas的操作
6 | const effects = {
7 | put,
8 | call
9 | }
10 |
11 | // 全局的redux actions
12 | const allActions = {}
13 | export const sagas = []
14 | export const reducers = {}
15 |
16 | // 获取全局的action
17 | const getAction = (actionName) => allActions[actionName]
18 |
19 | // 获取全局的state
20 | const getState = function*(child) {
21 | const get = state => state[child]
22 | return yield select(get)
23 | }
24 |
25 | /**
26 | * 创建reduce时自动关联saga
27 | */
28 | export default reduxer => (...args) => {
29 | const redux = reduxer.call(null, ...args)
30 | const name = underScoreToCamel(args[0])
31 |
32 | allActions[name] = redux.actions
33 | reducers[name] = redux.reducer
34 |
35 | return conf => {
36 | const watch = createWatcher(redux, conf)
37 | sagas.push(watch)
38 | return redux.actions
39 | }
40 | }
41 |
42 | /**
43 | * add watcher
44 | * @param redux
45 | * @param conf
46 | * @return {IterableIterator}
47 | */
48 | function* createWatcher(redux, conf) {
49 | yield takeLatest(redux.types.START, function* ({ payload }) {
50 | const options = config()
51 | // console.log('react-redux-config', options)
52 |
53 | conf = conf || {}
54 | let { url, data, method, headers = {}, extract = {}, onResult, onAfter, onError, fetch } = conf
55 |
56 | const callbackConfig = {
57 | ...effects,
58 | getAction: getAction,
59 | getState: getState,
60 | }
61 |
62 | try {
63 | // url处理
64 | url = typeof url === 'function' ? yield url(payload, callbackConfig) : url
65 | method = method ? method.toUpperCase() : 'GET'
66 |
67 | // data处理
68 | if (typeof data === 'function') {
69 | data = yield call(data, payload, callbackConfig)
70 | }
71 |
72 | // GET方法下,data合并到url中
73 | if (method === 'GET' && data) {
74 | url += url.indexOf('?') === -1 ? '?' : '&'
75 | url += obj2params(data)
76 | }
77 |
78 | let result
79 |
80 | // fetch方法是否定义
81 | const fetchMethod = fetch ? fetch : options.fetchMethod
82 | if (fetchMethod && url) {
83 | result = yield call(fetchMethod, {
84 | url,
85 | method,
86 | data,
87 | headers,
88 | ...extract,
89 | })
90 | }
91 |
92 | // data handler
93 | if (onResult) {
94 | const fallbackResult = yield call(onResult, result, payload, callbackConfig)
95 |
96 | // 判断fallbackResult不是null, undefined, 即使是空也要采用
97 | result = notNullOrUndefiend(fallbackResult)
98 | ? fallbackResult
99 | : result
100 | }
101 |
102 | // autoActions
103 | if (options.autoActions) {
104 | yield put(redux.actions.success(result))
105 | }
106 |
107 | // after data handled callback
108 | if (onAfter) {
109 | yield call(onAfter, result, payload, callbackConfig)
110 | }
111 | } catch (err) {
112 | // autoActions
113 | if (options.autoActions) {
114 | yield put(redux.actions.reset())
115 | }
116 |
117 | // error handler
118 | if (onError) {
119 | yield call(onError, err, payload, callbackConfig)
120 | } else if (options.catchError) {
121 | yield call(console.log, err, payload, callbackConfig)
122 | }
123 | }
124 | })
125 | }
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/libs/helper.js:
--------------------------------------------------------------------------------
1 | import { createAction, handleActions } from 'redux-actions'
2 | import Immutable from 'seamless-immutable'
3 |
4 | /**
5 | * @desc normal
6 | * @param actionName {string}, e.g. load_item
7 | * @param defaultData {object}
8 | */
9 | export const buildRedux = (actionName, defaultData = {}) => {
10 | const initialState = () =>
11 | Immutable({
12 | loading: false,
13 | error: false,
14 | success: false,
15 | params: null,
16 | data: {},
17 | ...defaultData,
18 | })
19 |
20 | const START = `${actionName}_START`
21 | const SUCCESS = `${actionName}_SUCCESS`
22 | const ERROR = `${actionName}_ERROR`
23 | const RESET = `${actionName}_REST`
24 |
25 | const start = createAction(START, (params = null) => ({params}))
26 | const reset = createAction(RESET)
27 | const success = createAction(SUCCESS, data => ({ data }))
28 | const error = createAction(ERROR, errorMessage => ({ errorMessage }))
29 |
30 | const reducer = handleActions(
31 | {
32 | [START]: (state, action) =>
33 | state.merge({
34 | loading: true,
35 | error: false,
36 | success: false,
37 | params: action.payload && action.payload.params,
38 | }),
39 | [SUCCESS]: (state, action) =>
40 | state.merge({
41 | loading: false,
42 | error: false,
43 | success: true,
44 | data: action.payload && action.payload.data,
45 | }),
46 | [ERROR]: (state, action) =>
47 | Immutable({
48 | loading: false,
49 | error: true,
50 | success: false,
51 | }),
52 | [RESET]: (state, action) => initialState(),
53 | },
54 | initialState(),
55 | )
56 |
57 | return {
58 | actions: {
59 | start,
60 | success,
61 | error,
62 | reset,
63 | },
64 | types: {
65 | START,
66 | SUCCESS,
67 | ERROR,
68 | RESET,
69 | },
70 | reducer,
71 | }
72 | }
73 |
74 | /**
75 | *
76 | * @param actionName {string}, e.g. list_vehicle
77 | * @param defaultData {object}
78 | * @return {{types: {SUCCESS: string, START: string, ERROR: string, RESET: string}, reducer: Function, actions:
79 | * {success: actionCreator, start: actionCreator, reset: actionCreator, error: actionCreator}}}
80 | */
81 | export const buildListRedux = (actionName, defaultData = {}) => {
82 | const initialState = () =>
83 | Immutable({
84 | loading: false,
85 | error: false,
86 | success: false,
87 | params: {},
88 | data: {
89 | page: 1,
90 | per_page: 10,
91 | total_count: 0,
92 | total_page: 0,
93 | entries: [],
94 | },
95 | ...defaultData,
96 | })
97 |
98 | const START = `${actionName}_LIST_START`
99 | const SUCCESS = `${actionName}_LIST_SUCCESS`
100 | const RESET = `RESET_${actionName}_LIST`
101 | const ERROR = `${actionName}_LIST_ERROR`
102 |
103 | const start = createAction(START, (page, limit, params) => ({
104 | page,
105 | limit,
106 | params,
107 | }))
108 | const success = createAction(SUCCESS, data => ({ data }))
109 | const reset = createAction(RESET)
110 | const error = createAction(ERROR)
111 |
112 | const reducer = handleActions(
113 | {
114 | [START]: (state, action) =>
115 | state.merge({
116 | loading: true,
117 | success: false,
118 | params: action.payload && action.payload.params,
119 | }),
120 | [SUCCESS]: (state, action) => {
121 | return state.merge({
122 | loading: false,
123 | success: true,
124 | data: action.payload && action.payload.data,
125 | })
126 | },
127 | [ERROR]: state => state.merge({ loading: false, error: true, success: false }),
128 | [RESET]: state => initialState(),
129 | },
130 | initialState(),
131 | )
132 |
133 | return {
134 | reducer,
135 | types: {
136 | START,
137 | SUCCESS,
138 | RESET,
139 | ERROR,
140 | },
141 | actions: {
142 | start,
143 | success,
144 | error,
145 | reset,
146 | },
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | react-redux-creator
2 | =====================
3 | 集成了react-router, react-redux, redux, redux-actions, redux-saga, seamless-immutable, connected-react-router, 简化了redux/异步处理和路由集成的配置工作,开箱即用
4 |
5 |
6 |
7 | > 使用redux常常需要创建大量的常量字面量,手动创建actions, 以及相应的reducers进行数据处理,并且redux跟路由配置对复杂难懂的初始化工作。
8 | >
9 | > react-redux-creator简化redux的使用流程,降低集成的难度,对于redux的创建,只提供了一个API即buildRedux完成并集成了异步数据请求。提供了Provider组件直接集成react-router。
10 |
11 |
12 |
13 |
14 | * buildRedux提供了创建reducer以及redux-saga的异步处理
15 | * Provider集成了create store以及react-router
16 | * connect方法简化了bindActionsCreator的使用
17 | * 自动合并sagas与reducers
18 | * fetch自定义
19 |
20 |
21 |
22 |
23 | ## 安装
24 |
25 | ```terminal
26 | yarn install react-redux-creator
27 | # or
28 | npm install react-redux-creator
29 | ```
30 |
31 |
32 |
33 | ## API
34 |
35 | **仅4个API, 1个非必要API**
36 |
37 | - config
38 | - Provider
39 | - connect
40 | - buildRedux
41 | - createStore(optional)
42 |
43 |
44 | ### 1. config(options)
45 |
46 | 初始化方法
47 |
48 | | options | type | description |
49 | | ----------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
50 | | fetchMethod | (config) => Promise
config: {
url: string,
method: string, // optional, default "GET"
data: object, // optional
headers: object, // optional
extract: object, // optional
} | 全局fetch方法,
通常应用的请求有公共的处理方式,
比如针对400等错误的处理,此处定义通用的网络请求fetch方法 |
51 | | logger | boolean | 默认"true", 是否开启redux-logger |
52 | | catchError | boolean | 默认"true", 是否自动加redux error log |
53 | | history | 'browser', 'hash', 'memory' | 默认'browser'路由方式,详细见[history](https://github.com/ReactTraining/history) |
54 | | autoActions | boolean(optional) | 默认"true", 自动执行success, reset方法 |
55 | | middleware | Array(optional) | redux中间件 |
56 |
57 |
58 | ***example***
59 |
60 | ```javascript
61 | import { config } from 'react-redux-creator'
62 | import fetch from './utils/fetch'
63 |
64 | config({
65 | fetchMethod: fetch, // 全局配置fetch
66 | history: 'browser', // 默认
67 | logger: true, // 开启redux-logger
68 | autoActions: true, // 是否开启saga自动success和错误的reset
69 | })
70 | ```
71 |
72 |
73 |
74 | ### 2. Provider 组件
75 |
76 | 集成路由
77 |
78 | | props | type | description |
79 | | --------- | ---------------------------------------- | ----------- |
80 | | routes | React.Component \| () => React.Component | 路由配置 |
81 | | initState | object(optional) | 初始化state |
82 |
83 |
84 | ***example***
85 |
86 | ```javascript
87 | ReactDOM.render(
88 | , {/* 配置路由 */}
89 | document.getElementById('app'),
90 | )
91 | ```
92 |
93 |
94 | ### 3. connect(mapStateToProps, mapActionsToProps)(component)
95 |
96 | 简化了mapActionsToProps, 自动合并了bindActionCreators的处理
97 |
98 | | arguments | types | description |
99 | | ----------------- | ----------------- | ---------------------------- |
100 | | mapStateToProps | (state) => object | 同react-redux方法 |
101 | | mapActionsToProps | object | 自动合并了bindActionCreators |
102 | | component | React.Component | 同react-redux方法 |
103 |
104 |
105 |
106 | ### 4. buildRedux(actionName, initState)(config)
107 |
108 | 返回 *start(params)*, *success(data)*, *reset()*, *error()* 方法以供调用
109 |
110 | 默认初始化数据
111 | ```javascript
112 | {
113 | loading: false, // 异步加载状态, start()方法后会变true
114 | success: false, // 异步成功状态, success()方法后变true
115 | error: false, // 错误状态,error()方法后变true
116 | params: null, // start(params)方法传入
117 | data: null, // success(data)方法传入
118 | }
119 |
120 | // initState可扩展初始化数据
121 | ```
122 |
123 | | arguments | child | type | required | description |
124 | | ---------- | -------- | ------------------------------------------------------ | -------- | ------------------------------------------------------------ |
125 | | actionName | - | string | Y | redux的action字面量以及存储在state的数据key |
126 | | initState | | object | N | 初始化数据, 默认(挂载到state中)包括
{
loading: false,
error: false,
success: false,
params: null,
data: null,
} |
127 | | config | | | | saga异步请求 |
128 | | | url | string \| function*(payload, callbackConfig) => string | N | callbackConfig 见下表 |
129 | | | method | string | N | POST, GET, PUT, DELETE... |
130 | | | data | object \| function*(payload, callbackConfig) => object | N | GET自动转为url search,
其他方式则为放body的数据 |
131 | | | headers | object | N | 请求headers (传给fetch方法) |
132 | | | extract | object | N | 请求扩展项 (传给fetch方法) |
133 | | | onResult | function*(data, payload, callbackConfig) => object | N | 当fetch请求完(如果有url,
fetch后,否则直接到该方法),
数据处理后返回给saga,
自动调用redux success方法,
回传到reducer 并最终合并到
state下。如果方法没有数据,
则默认使用原始的data。
callbackConfig 见下表 |
134 | | | onAfter | function*(data, payload, callbackConfig) => object | N | onResult完成以后执行,
在这里可以继续执行其他
的异步方法或者发起其他action,
callbackConfig 见下表 |
135 | | | onError | function*(err, payload, callbackConfig) => any | N | 错误异常处理,
自动调用redux的reset方法
也可以在这里手动执行error方法
callbackConfig 见下表 |
136 |
137 | **url**, **data**, **onResult**, **onAfter**, **onError** 都可接受function或者generator function,
138 | 如果有异步处理,请使用function* 配合yield call(function, ...arguments) 或者 yield put(action)使用
139 |
140 | #### callbackConfig
141 |
142 | | param | type | description |
143 | | --------- | ---------------------- | ------------------------------------------------------ |
144 | | put | function | redux-saga的effect, 发起dispatch |
145 | | call | function | redux-saga的effect, 发起异步处理 |
146 | | getAction | (actionName) => Action | 通过actionName获取action(start, success, error, reset) |
147 | | getState | (actionName) => State | 通过actionName获取state节点数据 |
148 |
149 |
150 | ### 5. createStore(initState)
151 |
152 | - 在taro等框架无法使用提供Provider,可以使用createStore方法来生成store, 集成在项目当中
153 |
154 |
155 | ## 使用
156 |
157 | [完整示例](https://github.com/joyerz/react-redux-creator/tree/examples)
158 |
159 | ### index.jsx(entry)
160 |
161 | ```javascript
162 | import '@babel/polyfill'
163 |
164 | import React from 'react'
165 | import ReactDOM from 'react-dom'
166 | import { Provider, config } from 'react-redux-creator'
167 | import fetch from './utils/fetch'
168 | import routes from './routes'
169 |
170 | // 初始化react-redux-creator
171 | config({
172 | fetchMethod: fetch, // 全局定义fetch请求方法
173 | logger: true, // 是否开启redux-logger
174 | history: 'browser', // 路由的history
175 | })
176 |
177 | ReactDOM.render(
178 | , {/* 配置路由 */}
179 | document.getElementById('app'),
180 | )
181 |
182 |
183 | ```
184 |
185 |
186 |
187 |
188 | ### routes(src/routes/index.jsx)
189 |
190 |
191 |
192 | ```javascript
193 | import * as React from 'react'
194 |
195 | import { Route, Switch } from 'react-router'
196 | import Home from '../pages/home'
197 |
198 | const routes = () => {
199 | return (
200 |
201 |
202 |
203 | )
204 | }
205 | export default routes
206 | ```
207 |
208 |
209 |
210 |
211 |
212 | ### redux (src/pages/home/redux.js)
213 |
214 |
215 |
216 | ```javascript
217 | import { buildRedux } from 'react-redux-creator'
218 | import { obj2params } from '../utils/objectHelper'
219 |
220 | export const companyAddRedux = buildRedux('companyAdd')({
221 | onResult: (data, payload, config) => ({ ..handled data })
222 | })
223 |
224 | export const companyListRedux = buildRedux('companyList' )({
225 | url: function*(payload, config){
226 | const { page, limit } = payload
227 | const { getState } = config
228 | const state = yield getState('companyList') // e.g. { name: 1, ... }
229 | const params = obj2params(state.params)
230 |
231 | // 返回请求 '/api/company?page=1&page-size=10&name=1'
232 | return `/api/company?page=${page}&page-size=${limit}&${params}`
233 | },
234 | onResult: function*(data, payload, config) {
235 | // 对网络请求后的数据进行添加额外extract属性
236 | return {
237 | ...data,
238 | extract: 'extractData'
239 | }
240 | },
241 | onAfter: function* (data, payload, config) {
242 | const { put, getAction } = config
243 |
244 | // 发起其他的action
245 | yield put(getAction('companyAdd').start())
246 | },
247 | onError: (err) => console.log('Error', err)
248 | })
249 |
250 | // 备注:url, data, onResult, onAfetr, onError 都可接受function或者generator function,
251 | // 如果有异步处理,请使用function* 配合yield call(function, ...arguments) 或者 yield put(action)使用
252 |
253 | ```
254 |
255 |
256 |
257 |
258 | ### container (/src/pages/home/index.jsx)
259 |
260 |
261 |
262 | ```javascript
263 | import React from 'react'
264 | import { connect } from 'react-redux-creator'
265 | import { companyAddRedux, companyListRedux } from './redux'
266 |
267 | class Home extends React.Component {
268 | componentDidMount() {
269 | this.props.actionList()
270 | }
271 |
272 | render() {
273 | return Hello
274 | }
275 | }
276 |
277 | export default connect(
278 | state => ({
279 | companyList: state.companyList,
280 | }),
281 | {
282 | actionList: (params) => companyListRedux.start(params),
283 | actionAdd: (params) => companyAddRedux.start(params),
284 | },
285 | )(Home)
286 | ```
287 |
288 |
289 |
290 |
--------------------------------------------------------------------------------
/dist/index.js:
--------------------------------------------------------------------------------
1 | module.exports=function(t){var e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)r.d(n,o,function(e){return t[e]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=17)}([function(t,e){t.exports=function(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}},function(t,e){t.exports=require("redux-actions")},function(t,e,r){t.exports=r(12)},function(t,e){t.exports=require("redux")},function(t,e){t.exports=require("connected-react-router")},function(t,e){t.exports=require("react-redux")},function(t,e,r){var n;!function(){"use strict";var o=function t(e){var r,n="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element"),o={use_static:!1};function i(t){var e=Object.getPrototypeOf(t);return e?Object.create(e):{}}function a(t,e,r){Object.defineProperty(t,e,{enumerable:!1,configurable:!1,writable:!1,value:r})}function c(t,e){a(t,e,(function(){throw new v("The "+e+" method cannot be invoked on an Immutable data structure.")}))}function u(t){return"object"!=typeof t||(null===t||Boolean(Object.getOwnPropertyDescriptor(t,"__immutable_invariants_hold")))}function f(t,e){return t===e||t!=t&&e!=e}function s(t){return!(null===t||"object"!=typeof t||Array.isArray(t)||t instanceof Date)}"object"!=typeof(r=e)||Array.isArray(r)||null===r||void 0!==e.use_static&&(o.use_static=Boolean(e.use_static));var l=["setPrototypeOf"],h=l.concat(["push","pop","sort","splice","shift","unshift","reverse"]),p=["keys"].concat(["map","filter","slice","concat","reduce","reduceRight"]),d=l.concat(["setDate","setFullYear","setHours","setMilliseconds","setMinutes","setMonth","setSeconds","setTime","setUTCDate","setUTCFullYear","setUTCHours","setUTCMilliseconds","setUTCMinutes","setUTCMonth","setUTCSeconds","setYear"]);function v(t){this.name="MyError",this.message=t,this.stack=(new Error).stack}function y(t,e){for(var r in a(t,"__immutable_invariants_hold",!0),e)e.hasOwnProperty(r)&&c(t,e[r]);return Object.freeze(t),t}function g(t,e){var r=t[e];a(t,e,(function(){return Y(r.apply(t,arguments))}))}function b(t,e,r){var n=r&&r.deep;if(t in this&&(n&&this[t]!==e&&s(e)&&s(this[t])&&(e=Y.merge(this[t],e,{deep:!0,mode:"replace"})),f(this[t],e)))return this;var o=S.call(this);return o[t]=Y(e),O(o)}v.prototype=new Error,v.prototype.constructor=Error;var m=Y([]);function w(t,e,r){var n=t[0];if(1===t.length)return b.call(this,n,e,r);var o,i=t.slice(1),a=this[n];if("object"==typeof a&&null!==a)o=Y.setIn(a,i,e);else{var c=i[0];o=""!==c&&isFinite(c)?w.call(m,i,e):M.call(I,i,e)}if(n in this&&a===o)return this;var u=S.call(this);return u[n]=o,O(u)}function O(t){for(var e in p){if(p.hasOwnProperty(e))g(t,p[e])}o.use_static||(a(t,"flatMap",j),a(t,"asObject",E),a(t,"asMutable",S),a(t,"set",b),a(t,"setIn",w),a(t,"update",U),a(t,"updateIn",N),a(t,"getIn",q));for(var r=0,n=t.length;r=e.length?r(new c(d,w,new a(void 0,t[w]))):s(t[w],e[w],r,n,d,w,p);for(;w=0?(s(t[o],e[o],r,n,d,o,p),x=u(x,a)):s(t[o],void 0,r,n,d,o,p)})),x.forEach((function(t){s(void 0,e[t],r,n,d,t,p)}))}p.length=p.length-1}else t!==e&&("number"===y&&isNaN(t)&&isNaN(e)||r(new o(d,t,e)))}function l(t,e,r,n){return n=n||[],s(t,e,(function(t){t&&n.push(t)}),r),n.length?n:void 0}function h(t,e,r){if(t&&e&&r&&r.kind){for(var n=t,o=-1,i=r.path?r.path.length-1:0;++o0&&void 0!==arguments[0]?arguments[0]:{},e=Object.assign({},A,t),r=e.logger,n=e.stateTransformer,o=e.errorTransformer,i=e.predicate,a=e.logErrors,c=e.diffPredicate;if(void 0===r)return function(){return function(t){return function(e){return t(e)}}};if(t.getState&&t.dispatch)return function(){return function(t){return function(e){return t(e)}}};var u=[];return function(t){var r=t.getState;return function(t){return function(f){if("function"==typeof i&&!i(r,f))return t(f);var s={};u.push(s),s.started=x.now(),s.startedTime=new Date,s.prevState=n(r()),s.action=f;var l=void 0;if(a)try{l=t(f)}catch(t){s.error=o(t)}else l=t(f);s.took=x.now()-s.started,s.nextState=n(r());var h=e.diff&&"function"==typeof c?c(r,f):e.diff;if(y(u,Object.assign({},e,{diff:h})),u.length=0,s.error)throw s.error;return l}}}}var b,m,w=function(t,e){return function(t,e){return new Array(e+1).join(t)}("0",e-t.toString().length)+t},O=function(t){return w(t.getHours(),2)+":"+w(t.getMinutes(),2)+":"+w(t.getSeconds(),2)+"."+w(t.getMilliseconds(),3)},x="undefined"!=typeof performance&&null!==performance&&"function"==typeof performance.now?performance:Date,j="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},P=function(t){if(Array.isArray(t)){for(var e=0,r=Array(t.length);e0&&void 0!==arguments[0]?arguments[0]:{},e=t.dispatch,r=t.getState;return"function"==typeof e||"function"==typeof r?g()({dispatch:e,getState:r}):void 0};e.defaults=A,e.createLogger=g,e.logger=k,e.default=k,Object.defineProperty(e,"__esModule",{value:!0})}(e)}).call(this,r(16))},function(t,e,r){var n=function(t){"use strict";var e=Object.prototype,r=e.hasOwnProperty,n="function"==typeof Symbol?Symbol:{},o=n.iterator||"@@iterator",i=n.asyncIterator||"@@asyncIterator",a=n.toStringTag||"@@toStringTag";function c(t,e,r,n){var o=e&&e.prototype instanceof s?e:s,i=Object.create(o.prototype),a=new x(n||[]);return i._invoke=function(t,e,r){var n="suspendedStart";return function(o,i){if("executing"===n)throw new Error("Generator is already running");if("completed"===n){if("throw"===o)throw i;return P()}for(r.method=o,r.arg=i;;){var a=r.delegate;if(a){var c=m(a,r);if(c){if(c===f)continue;return c}}if("next"===r.method)r.sent=r._sent=r.arg;else if("throw"===r.method){if("suspendedStart"===n)throw n="completed",r.arg;r.dispatchException(r.arg)}else"return"===r.method&&r.abrupt("return",r.arg);n="executing";var s=u(t,e,r);if("normal"===s.type){if(n=r.done?"completed":"suspendedYield",s.arg===f)continue;return{value:s.arg,done:r.done}}"throw"===s.type&&(n="completed",r.method="throw",r.arg=s.arg)}}}(t,r,a),i}function u(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}t.wrap=c;var f={};function s(){}function l(){}function h(){}var p={};p[o]=function(){return this};var d=Object.getPrototypeOf,v=d&&d(d(j([])));v&&v!==e&&r.call(v,o)&&(p=v);var y=h.prototype=s.prototype=Object.create(p);function g(t){["next","throw","return"].forEach((function(e){t[e]=function(t){return this._invoke(e,t)}}))}function b(t){var e;this._invoke=function(n,o){function i(){return new Promise((function(e,i){!function e(n,o,i,a){var c=u(t[n],t,o);if("throw"!==c.type){var f=c.arg,s=f.value;return s&&"object"==typeof s&&r.call(s,"__await")?Promise.resolve(s.__await).then((function(t){e("next",t,i,a)}),(function(t){e("throw",t,i,a)})):Promise.resolve(s).then((function(t){f.value=t,i(f)}),(function(t){return e("throw",t,i,a)}))}a(c.arg)}(n,o,e,i)}))}return e=e?e.then(i,i):i()}}function m(t,e){var r=t.iterator[e.method];if(void 0===r){if(e.delegate=null,"throw"===e.method){if(t.iterator.return&&(e.method="return",e.arg=void 0,m(t,e),"throw"===e.method))return f;e.method="throw",e.arg=new TypeError("The iterator does not provide a 'throw' method")}return f}var n=u(r,t.iterator,e.arg);if("throw"===n.type)return e.method="throw",e.arg=n.arg,e.delegate=null,f;var o=n.arg;return o?o.done?(e[t.resultName]=o.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=void 0),e.delegate=null,f):o:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,f)}function w(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function O(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function x(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(w,this),this.reset(!0)}function j(t){if(t){var e=t[o];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var n=-1,i=function e(){for(;++n=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var c=r.call(i,"catchLoc"),u=r.call(i,"finallyLoc");if(c&&u){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),O(r),f}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;O(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:j(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),f}},t}(t.exports);try{regeneratorRuntime=n}catch(t){Function("r","regeneratorRuntime = r")(n)}},function(t,e){t.exports=function(t){if(Array.isArray(t)){for(var e=0,r=new Array(t.length);e1?e-1:0),n=1;n1?e-1:0),n=1;n1?e-1:0),n=1;n2?r-2:0),o=2;o2?r-2:0),o=2;o1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";if("object"!==K()(t)||!t)return"";var n=[];return Object.keys(t).forEach((function(e){void 0!==t[e]&&null!==t[e]&&(t[e]instanceof Object?n.push("".concat(e,"=").concat(JSON.stringify(t[e]))):n.push("".concat(e,"=").concat(t[e])))})),e+n.join("&")+r}var J=function(t){return t.split("_").map((function(t,e){return t.length>0?0===e?t:t[0].toUpperCase()+t.substring(1).toLowerCase():""})).join("")},W=v.a.mark(ot);function Q(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,n)}return r}function V(t){for(var e=1;e=0;s--){var l=o[s];"."===l?ft(o,s):".."===l?(ft(o,s),f++):f&&(ft(o,s),f--)}if(!c)for(;f--;f)o.unshift("..");!c||""===o[0]||o[0]&&ut(o[0])||o.unshift("");var h=o.join("/");return r&&"/"!==h.substr(-1)&&(h+="/"),h};var lt=function(t,e){if(!t)throw new Error("Invariant failed")};function ht(t){return"/"===t.charAt(0)?t:"/"+t}function pt(t){return"/"===t.charAt(0)?t.substr(1):t}function dt(t,e){return function(t,e){return 0===t.toLowerCase().indexOf(e.toLowerCase())&&-1!=="/?#".indexOf(t.charAt(e.length))}(t,e)?t.substr(e.length):t}function vt(t){return"/"===t.charAt(t.length-1)?t.slice(0,-1):t}function yt(t){var e=t.pathname,r=t.search,n=t.hash,o=e||"/";return r&&"?"!==r&&(o+="?"===r.charAt(0)?r:"?"+r),n&&"#"!==n&&(o+="#"===n.charAt(0)?n:"#"+n),o}function gt(t,e,r,n){var o;"string"==typeof t?(o=function(t){var e=t||"/",r="",n="",o=e.indexOf("#");-1!==o&&(n=e.substr(o),e=e.substr(0,o));var i=e.indexOf("?");return-1!==i&&(r=e.substr(i),e=e.substr(0,i)),{pathname:e,search:"?"===r?"":r,hash:"#"===n?"":n}}(t)).state=e:(void 0===(o=ct({},t)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==e&&void 0===o.state&&(o.state=e));try{o.pathname=decodeURI(o.pathname)}catch(t){throw t instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):t}return r&&(o.key=r),n?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=st(o.pathname,n.pathname)):o.pathname=n.pathname:o.pathname||(o.pathname="/"),o}function bt(){var t=null;var e=[];return{setPrompt:function(e){return t=e,function(){t===e&&(t=null)}},confirmTransitionTo:function(e,r,n,o){if(null!=t){var i="function"==typeof t?t(e,r):t;"string"==typeof i?"function"==typeof n?n(i,o):o(!0):o(!1!==i)}else o(!0)},appendListener:function(t){var r=!0;function n(){r&&t.apply(void 0,arguments)}return e.push(n),function(){r=!1,e=e.filter((function(t){return t!==n}))}},notifyListeners:function(){for(var t=arguments.length,r=new Array(t),n=0;ne?r.splice(e,r.length-e,n):r.push(n),s({action:"PUSH",location:n,index:e,entries:r})}}))},replace:function(t,e){var n=gt(t,e,l(),y.location);f.confirmTransitionTo(n,"REPLACE",r,(function(t){t&&(y.entries[y.index]=n,s({action:"REPLACE",location:n}))}))},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(t){var e=y.index+t;return e>=0&&e0&&void 0!==arguments[0]?arguments[0]:{},e=Ht(t,et,tt);return p({store:e}),e};e.default={buildRedux:(Gt=function(t){var e,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=function(){return c()(f({loading:!1,error:!1,success:!1,params:null,data:{}},r))},a="".concat(t,"_START"),u="".concat(t,"_SUCCESS"),s="".concat(t,"_ERROR"),l="".concat(t,"_REST"),h=Object(i.createAction)(a,(function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return{params:t}})),p=Object(i.createAction)(l),d=Object(i.createAction)(u,(function(t){return{data:t}})),v=Object(i.createAction)(s,(function(t){return{errorMessage:t}})),y=Object(i.handleActions)((e={},o()(e,a,(function(t,e){return t.merge({loading:!0,error:!1,success:!1,params:e.payload&&e.payload.params})})),o()(e,u,(function(t,e){return t.merge({loading:!1,error:!1,success:!0,data:e.payload&&e.payload.data})})),o()(e,s,(function(t,e){return c()({loading:!1,error:!0,success:!1})})),o()(e,l,(function(t,e){return n()})),e),n());return{actions:{start:h,success:d,error:v,reset:p},types:{START:a,SUCCESS:u,ERROR:s,RESET:l},reducer:y}},function(){for(var t=arguments.length,e=new Array(t),r=0;r