├── .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