├── scripts ├── webpack.config.js └── webpack.tip.js ├── lib ├── index.d.ts ├── index.js └── index.js.map ├── public └── index.d.ts ├── .gitignore ├── src ├── simple.js └── index.js ├── package.json ├── README.md ├── .eslintrc.js └── README-CN.md /scripts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const tip = require('./webpack.tip')(); 2 | const nodeExternals = require('webpack-node-externals'); 3 | 4 | module.exports = { 5 | target: 'web', 6 | mode: tip.mode.production, 7 | devtool: tip.devtool.sourceMap, 8 | entry: { 9 | index: tip.paths.entry, 10 | }, 11 | output: { 12 | libraryTarget: 'umd', 13 | path: tip.paths.output, 14 | pathinfo: true, 15 | filename: 'index.js', 16 | chunkFilename: '[name]_.chunk.js', 17 | }, 18 | externals: [nodeExternals()], 19 | resolve: { 20 | extensions: tip.resolve.extensions, 21 | alias: tip.resolve.alias, 22 | plugins: [], 23 | }, 24 | module: { 25 | rules: [ 26 | tip.module.rules.eslint, 27 | tip.module.rules.cssLoader, 28 | tip.module.rules.stylusLoader, 29 | tip.module.rules.urlLoader, 30 | tip.module.rules.fileLoader, 31 | tip.module.rules.sourceMapLoader, 32 | tip.module.rules.tsLodaer, 33 | tip.module.rules.babelLoaderBuild, 34 | ], 35 | }, 36 | devServer: tip.devServer, 37 | plugins: [ 38 | tip.plugins.DefinePlugin, 39 | tip.plugins.FastUglifyJsPluginDev, 40 | tip.plugins.CopyWebpackPlugin, 41 | ], 42 | watchOptions: { 43 | ignored: /node_modules/, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IStore { 4 | useContext: Function; 5 | dispatch: Function; 6 | state: Object; 7 | initalState: Object; 8 | subscribe: Function; 9 | connect: ( 10 | Component: React.Component, 11 | mapStateToProps?: Function, 12 | mapDispatchToProps?: Function 13 | ) => React.Component; 14 | } 15 | 16 | interface IReactHooksRedux { 17 | Provider: React.Component; 18 | store: IStore; 19 | } 20 | interface IReactHooksReduxParams { 21 | isDev?: Boolean; 22 | reducer?: (state: Object, action: Function) => Object; 23 | initialState?: Object; 24 | autoSave?: { localName: String; keys: Array }; 25 | middleware?: Object | undefined; 26 | } 27 | 28 | interface IStorage { 29 | localName: string; 30 | save: (obj: any, key: string) => void; 31 | load: (key: string) => any; 32 | clear: (key: string) => void; 33 | } 34 | 35 | export default function createStore( 36 | params: IReactHooksReduxParams 37 | ): IReactHooksRedux; 38 | 39 | export function middlewareLog( 40 | store: Object, 41 | oldState: Object, 42 | nextState: Object, 43 | action: Object 44 | ): void; 45 | 46 | export const reducerInAction: (state: Object, action: Function) => Object; 47 | 48 | export const storage: IStorage; 49 | -------------------------------------------------------------------------------- /public/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface IStore { 4 | useContext: Function; 5 | dispatch: Function; 6 | state: Object; 7 | initalState: Object; 8 | subscribe: Function; 9 | connect: ( 10 | Component: React.Component, 11 | mapStateToProps?: Function, 12 | mapDispatchToProps?: Function 13 | ) => React.Component; 14 | } 15 | 16 | interface IReactHooksRedux { 17 | Provider: React.Component; 18 | store: IStore; 19 | } 20 | interface IReactHooksReduxParams { 21 | isDev?: Boolean; 22 | reducer?: (state: Object, action: Function) => Object; 23 | initialState?: Object; 24 | autoSave?: { localName: String; keys: Array }; 25 | middleware?: Object | undefined; 26 | } 27 | 28 | interface IStorage { 29 | localName: string; 30 | save: (obj: any, key: string) => void; 31 | load: (key: string) => any; 32 | clear: (key: string) => void; 33 | } 34 | 35 | export default function createStore( 36 | params: IReactHooksReduxParams 37 | ): IReactHooksRedux; 38 | 39 | export function middlewareLog( 40 | store: Object, 41 | oldState: Object, 42 | nextState: Object, 43 | action: Object 44 | ): void; 45 | 46 | export const reducerInAction: (state: Object, action: Function) => Object; 47 | 48 | export const storage: IStorage; 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | **/dll 6 | **/__pycache__ 7 | **/.cache 8 | dist 9 | **/dist 10 | **/build 11 | **/ignores 12 | .webpack_cache 13 | **/.webpack_cache 14 | .viminfo 15 | .vimsession 16 | *.sln 17 | .idea 18 | 19 | # Xcode 20 | 21 | build/ 22 | *.pbxuser 23 | !default.pbxuser 24 | *.mode1v3 25 | !default.mode1v3 26 | *.mode2v3 27 | !default.mode2v3 28 | *.perspectivev3 29 | !default.perspectivev3 30 | xcuserdata 31 | *.xccheckout 32 | *.moved-aside 33 | DerivedData 34 | *.hmap 35 | *.ipa 36 | *.xcuserstate 37 | project.xcworkspace 38 | 39 | # Android/IntelliJ 40 | # 41 | build/ 42 | .idea 43 | .gradle 44 | local.properties 45 | *.iml 46 | 47 | # 48 | node_modules/ 49 | **/node_modules/ 50 | npm-debug.log 51 | yarn-error.log 52 | 53 | # BUCK 54 | buck-out/ 55 | .buckd/ 56 | *.keystore 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/ 64 | 65 | */fastlane/report.xml 66 | */fastlane/Preview.html 67 | */fastlane/screenshots 68 | 69 | # VSCode Plugins 70 | .project 71 | .classpath 72 | .settings/ 73 | 74 | dist/v*/* 75 | 76 | .vscode/ 77 | android/app/release/ 78 | 79 | .eslintcache 80 | -------------------------------------------------------------------------------- /src/simple.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function middlewareLog(lastState, nextState, action, isDev) { 4 | if (isDev) { 5 | console.log( 6 | `%c|------- redux: ${action.type} -------|`, 7 | `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`, 8 | ); 9 | console.log('|--last:', lastState); 10 | console.log('|--next:', nextState); 11 | } 12 | } 13 | 14 | function reducerInAction(state, action) { 15 | if (typeof action.reducer === 'function') { 16 | return action.reducer(state); 17 | } 18 | return state; 19 | } 20 | 21 | export default function createStore(params) { 22 | const { isDev, reducer, initialState, middleware } = { 23 | isDev: false, 24 | reducer: reducerInAction, 25 | initialState: {}, 26 | middleware: params.isDev ? [middlewareLog] : undefined, 27 | ...params, 28 | }; 29 | const AppContext = React.createContext(); 30 | const store = { 31 | isDev, 32 | _state: initialState, 33 | useContext: function() { 34 | return React.useContext(AppContext); 35 | }, 36 | dispatch: undefined, 37 | getState: function() { 38 | return store._state; 39 | }, 40 | initialState, 41 | }; 42 | let isCheckedMiddleware = false; 43 | const middlewareReducer = function(lastState, action) { 44 | let nextState = reducer(lastState, action); 45 | if (!isCheckedMiddleware) { 46 | if (Object.prototype.toString.call(middleware) !== '[object Array]') { 47 | throw new Error("react-hooks-redux: middleware isn't Array"); 48 | } 49 | isCheckedMiddleware = true; 50 | } 51 | for (let i = 0; i < middleware.length; i++) { 52 | const newState = middleware[i](store, lastState, nextState, action); 53 | if (newState) { 54 | nextState = newState; 55 | } 56 | } 57 | store._state = nextState; 58 | return nextState; 59 | }; 60 | 61 | const Provider = props => { 62 | const [state, dispatch] = React.useReducer(middlewareReducer, initialState); 63 | if (!store.dispatch) { 64 | store.dispatch = async function(action) { 65 | if (typeof action === 'function') { 66 | await action(dispatch, store._state); 67 | } else { 68 | dispatch(action); 69 | } 70 | }; 71 | } 72 | return ; 73 | }; 74 | return { Provider, store }; 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hooks-redux", 3 | "version": "0.10.4", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "build": "prod=1 webpack --config scripts/webpack.config.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@babel/core": "^7.0.0", 14 | "@babel/plugin-proposal-class-properties": "^7.0.0", 15 | "@babel/plugin-proposal-decorators": "^7.0.0", 16 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 17 | "@babel/plugin-transform-runtime": "^7.0.0", 18 | "@babel/polyfill": "^7.0.0", 19 | "@babel/preset-env": "^7.0.0", 20 | "@babel/preset-react": "^7.0.0", 21 | "@babel/preset-stage-0": "^7.0.0", 22 | "@babel/runtime": "^7.0.0", 23 | "@types/jest": "^23.3.1", 24 | "@types/react": "^16.7.2", 25 | "@types/react-dom": "^16.0.9", 26 | "@types/react-native": "^0.57.7", 27 | "@types/react-test-renderer": "^16.0.2", 28 | "autoprefixer": "^9.0.0", 29 | "babel-eslint": "^9.0.0", 30 | "babel-jest": "23.4.2", 31 | "babel-loader": "8.0.0-beta.0", 32 | "babel-plugin-module-resolver": "^3.1.1", 33 | "babel-plugin-styled-components": "^1.7.1", 34 | "babel-plugin-transform-class-properties": "^6.24.1", 35 | "clean-webpack-plugin": "^0.1.19", 36 | "copy-webpack-plugin": "^4.5.2", 37 | "css-loader": "^1.0.0", 38 | "eslint": "^5.5.0", 39 | "eslint-loader": "^2.1.0", 40 | "eslint-plugin-react": "^7.11.1", 41 | "extract-text-webpack-plugin": "^3.0.2", 42 | "fast-uglifyjs-plugin": "^0.3.0", 43 | "file-loader": "^1.1.11", 44 | "fs-extra": "^7.0.0", 45 | "html-webpack-plugin": "^3.2.0", 46 | "jest": "23.5.0", 47 | "postcss-flexbugs-fixes": "^3.3.1", 48 | "postcss-loader": "^2.1.5", 49 | "pre-commit": "^1.2.2", 50 | "react": "^16.7.0-alpha.0", 51 | "react-dev-utils": "^5.0.2", 52 | "react-dom": "^16.7.0-alpha.0", 53 | "react-hot-loader": "^4.3.3", 54 | "react-test-renderer": "16.4.1", 55 | "source-map-loader": "^0.2.3", 56 | "style-loader": "^0.21.0", 57 | "ts-jest": "^23.1.4", 58 | "ts-loader": "^4.4.2", 59 | "tsconfig-paths-webpack-plugin": "^3.2.0", 60 | "typescript": "^3.0.3", 61 | "url-loader": "^1.0.1", 62 | "webpack": "^4.16.1", 63 | "webpack-cli": "^3.1.0", 64 | "webpack-dev-server": "^3.1.7", 65 | "webpack-node-externals": "^1.7.2" 66 | }, 67 | "repository": { 68 | "type": "git", 69 | "url": "git+https://github.com/ymzuiku/react-hooks-redux.git" 70 | }, 71 | "keywords": [], 72 | "author": "", 73 | "license": "MIT", 74 | "bugs": { 75 | "url": "https://github.com/ymzuiku/react-hooks-redux/issues" 76 | }, 77 | "homepage": "https://github.com/ymzuiku/react-hooks-redux#readme" 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Chinese Document](README-CN.md) 2 | 3 | ## Example 4 | 5 | Example and Started: 6 | 7 | ```js 8 | import React from "react"; 9 | import ReactHookRedux from "react-hooks-redux"; 10 | 11 | const { Provider, store } = ReactHookRedux({ 12 | isDev: true, 13 | initialState: { name: "dog", age: 0 }, 14 | }); 15 | 16 | function actionOfAdd() { 17 | return { 18 | type: "add the count", 19 | reducer(state) { 20 | return { ...state, age: state.age + 1 }; 21 | }, 22 | }; 23 | } 24 | 25 | function Button() { 26 | function handleAdd() { 27 | store.dispatch(actionOfAdd()); //dispatch 28 | } 29 | return ; 30 | } 31 | 32 | function Page() { 33 | const state = store.useContext(); 34 | return ( 35 |
36 | {state.age}
38 | ); 39 | } 40 | 41 | export default function App() { 42 | return ( 43 | 44 | 45 | 46 | ); 47 | } 48 | ``` 49 | 50 | ## async/await example 51 | 52 | ```js 53 | import React from "react"; 54 | import ReactHookRedux, { reducerInAction, devLog } from "react-hooks-redux"; 55 | 56 | const { Provider, store } = ReactHookRedux({ 57 | isDev: true, // default is false 58 | initialState: { count: 0, asyncCount: 0 }, // default is {} 59 | reducer: reducerInAction, // default is reducerInAction 60 | middleware: [devLog], // default is [devLog] 61 | actions: {}, // default is {} 62 | }); 63 | 64 | // 模拟异步操作 65 | function timeOutAdd(a) { 66 | return new Promise((cb) => setTimeout(() => cb(a + 1), 500)); 67 | } 68 | 69 | const actions = { 70 | // if return a function,like react-thunk: 71 | asyncAdd: () => async (dispatch, ownState) => { 72 | const asyncCount = await timeOutAdd(ownState.asyncCount); 73 | dispatch({ 74 | type: "asyncAdd", 75 | // if use reducerInAction, we can add reducer Function repeat reducer 76 | reducer(state) { 77 | return { 78 | ...state, 79 | asyncCount, 80 | }; 81 | }, 82 | }); 83 | }, 84 | }; 85 | 86 | function Item() { 87 | const state = store.useContext(); 88 | return
async-count: {state.asyncCount}
; 89 | } 90 | 91 | function Button() { 92 | async function handleAdd() { 93 | // 使用 async dispatch 94 | await store.dispatch(actions.asyncAdd()); 95 | } 96 | return ; 97 | } 98 | 99 | export default function App() { 100 | return ( 101 | 102 | 103 | ; 140 | } 141 | 142 | function Page() { 143 | const state = store.useContext(); 144 | const products = state.get("products"); 145 | 146 | return useCallback( 147 |
148 |
, 153 | [products] 154 | ); 155 | } 156 | 157 | export default function App() { 158 | return ( 159 | 160 | 161 | 162 | ); 163 | } 164 | ``` 165 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | !function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var r=t();for(var o in r)("object"==typeof exports?exports:e)[o]=r[o]}}(window,function(){return function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([ 2 | /*!**********************!*\ 3 | !*** ./src/index.js ***! 4 | \**********************/ 5 | /*! no static exports found */ 6 | /*! all exports used */ 7 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 8 | function(e,t,r){"use strict";var o=r(/*! @babel/runtime/helpers/interopRequireDefault */1);Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:v,t=(0,c.default)({},v,e),r=t.isDev,o=t.reducer,f=t.initialState,s=t.middleware,b=t.autoSave,y=l.default.createContext(),m={isDev:r,_state:f,useContext:function(){return l.default.useContext(y)},subscribe:d,dispatch:void 0,getState:function(){return m._state},onload:[],initialState:f,connect:function(e,t){return function(r){return function(o){var n=m.useContext(),a=e(n),u=t(m.dispatch,n),c=[];for(var f in a)c.push(a[f]);return l.default.useMemo(function(){return l.default.createElement(r,(0,i.default)({},a,u,o))},c)}}}},S=!1,h=function(e,t){if(!t)return e;var r=o(e,t);if(!S){if("[object Array]"!==Object.prototype.toString.call(s))throw new Error("react-hooks-redux: middleware isn't Array");S=!0}for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:b.localName,r=Object.prototype.toString.call(e);"[object Object]"===r?localStorage.setItem(t,JSON.stringify(e)):"[object String]"===r?localStorage.setItem(t,e):console.warn("Warn: storage.save() param is no a Object")},load:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:b.localName;try{var t=localStorage.getItem(e);if(t)return"string"==typeof t?JSON.parse(t):t}catch(e){console.warn("load last localSate error")}},clear:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:b.localName;localStorage.setItem(e,{})}};function g(e,t,r){t&&(b.localName=t),"[object Array]"!==Object.prototype.toString.call(r)&&console.warn("autoSaveStorageKeys: params is no a Array");var o=b.load(b.localName);"[object Object]"===Object.prototype.toString.call(o)&&e.onload.push(function(){e.dispatch({type:"localStorageLoad: IO",reducer:function(e){if(e&&e.toJS){var t=(0,c.default)({},e.toJS(),o);for(var r in t)e=e.set(r,t[r]);return e}return(0,c.default)({},e,o)}})});var n={};r.forEach(function(e){n[e]=void 0}),e.subscribe(function(){var t=e.getState();if(t&&t.toJS){var o={},a=!1;r.forEach(function(e){"[object Array]"===Object.prototype.toString.call(e)?o[e]=t.getIn(e):o[e]=t.get(e),n[e]!==o[e]&&(a=!0),n[e]=o[e]}),a&&b.save(o)}else{var u={},i=!0;r.forEach(function(e){u[e]=t[e],n[e]!==u[e]&&(i=!0),n[e]=u[e]}),i&&b.save(u)}})}function y(e,t,r,o){if(e.isDev&&!o.$NOLOG)if(console.log("%c|------- redux: ".concat(o.type," -------|"),"background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;"),!o.$OBJLOG&&r&&"function"==typeof r.toJS){var n={};if(r.map(function(e,t){n[t]=e}),o.$LASTLOG){var a={};t.map(function(e,t){a[t]=e}),console.log("|--last",a),console.log("|--next",n)}else console.log("|--",n)}else if(o.$LASTLOG){var u={};t.map(function(e,t){u[t]=e}),console.log("|--last",t),console.log("|--next",r)}else console.log("|--",r)}t.storage=b}, 9 | /*!***************************************************************!*\ 10 | !*** external "@babel/runtime/helpers/interopRequireDefault" ***! 11 | \***************************************************************/ 12 | /*! no static exports found */ 13 | /*! all exports used */ 14 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 15 | function(e,t){e.exports=require("@babel/runtime/helpers/interopRequireDefault")}, 16 | /*!*********************************************!*\ 17 | !*** external "@babel/runtime/regenerator" ***! 18 | \*********************************************/ 19 | /*! no static exports found */ 20 | /*! all exports used */ 21 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 22 | function(e,t){e.exports=require("@babel/runtime/regenerator")}, 23 | /*!**********************************************************!*\ 24 | !*** external "@babel/runtime/helpers/asyncToGenerator" ***! 25 | \**********************************************************/ 26 | /*! no static exports found */ 27 | /*! all exports used */ 28 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 29 | function(e,t){e.exports=require("@babel/runtime/helpers/asyncToGenerator")}, 30 | /*!*******************************************************!*\ 31 | !*** external "@babel/runtime/helpers/slicedToArray" ***! 32 | \*******************************************************/ 33 | /*! no static exports found */ 34 | /*! all exports used */ 35 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 36 | function(e,t){e.exports=require("@babel/runtime/helpers/slicedToArray")}, 37 | /*!*************************************************!*\ 38 | !*** external "@babel/runtime/helpers/extends" ***! 39 | \*************************************************/ 40 | /*! no static exports found */ 41 | /*! all exports used */ 42 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 43 | function(e,t){e.exports=require("@babel/runtime/helpers/extends")}, 44 | /*!******************************************************!*\ 45 | !*** external "@babel/runtime/helpers/objectSpread" ***! 46 | \******************************************************/ 47 | /*! no static exports found */ 48 | /*! all exports used */ 49 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 50 | function(e,t){e.exports=require("@babel/runtime/helpers/objectSpread")}, 51 | /*!************************!*\ 52 | !*** external "react" ***! 53 | \************************/ 54 | /*! no static exports found */ 55 | /*! all exports used */ 56 | /*! ModuleConcatenation bailout: Module is not an ECMAScript module */ 57 | function(e,t){e.exports=require("react")}])}); 58 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function reducerInAction(state, action) { 4 | if (typeof action.reducer === "function") { 5 | return action.reducer(state); 6 | } 7 | return state; 8 | } 9 | 10 | const subscribeCache = {}; 11 | let subscribeNum = 0; 12 | function subscribe(fn) { 13 | if (typeof fn !== "function") { 14 | throw new Error("react-hooks-redux: subscribe params need a function"); 15 | } 16 | subscribeNum++; 17 | subscribeCache[subscribeNum] = fn; 18 | function unSubscribe() { 19 | delete subscribeCache[subscribeNum]; 20 | } 21 | return unSubscribe; 22 | } 23 | 24 | function runSubscribes(state, action) { 25 | for (const k in subscribeCache) { 26 | subscribeCache[k](state, action); 27 | } 28 | } 29 | 30 | const defalutOptions = { 31 | isDev: false, 32 | reducer: reducerInAction, 33 | initialState: {}, 34 | middleware: [middlewareLog], 35 | autoSave: { item: undefined, keys: [] } 36 | }; 37 | 38 | export default function createStore(options = defalutOptions) { 39 | const { isDev, reducer, initialState, middleware, autoSave } = { 40 | ...defalutOptions, 41 | ...options 42 | }; 43 | const AppContext = React.createContext(); 44 | const store = { 45 | isDev, 46 | _state: initialState, 47 | useContext: function() { 48 | return React.useContext(AppContext); 49 | }, 50 | subscribe, 51 | dispatch: undefined, 52 | getState: function() { 53 | return store._state; 54 | }, 55 | onload: [], 56 | initialState, 57 | connect: function(mapStateToProps, mapDispatchToProps) { 58 | return function(Comp) { 59 | return function(props) { 60 | const imm = store.useContext(); 61 | const stateToProps = mapStateToProps(imm); 62 | const dispatchProps = mapDispatchToProps(store.dispatch, imm); 63 | const memoList = []; 64 | for (const k in stateToProps) { 65 | memoList.push(stateToProps[k]); 66 | } 67 | function render() { 68 | return ; 69 | } 70 | return React.useMemo(render, memoList); 71 | }; 72 | }; 73 | } 74 | }; 75 | let isCheckedMiddleware = false; 76 | const middlewareReducer = function(lastState, action) { 77 | if (!action) { 78 | return lastState; 79 | } 80 | let nextState = reducer(lastState, action); 81 | if (!isCheckedMiddleware) { 82 | if (Object.prototype.toString.call(middleware) !== "[object Array]") { 83 | throw new Error("react-hooks-redux: middleware isn't Array"); 84 | } 85 | isCheckedMiddleware = true; 86 | } 87 | for (let i = 0; i < middleware.length; i++) { 88 | const newState = middleware[i](store, lastState, nextState, action); 89 | if (newState) { 90 | nextState = newState; 91 | } 92 | } 93 | store._state = nextState; 94 | runSubscribes(nextState, action); 95 | return nextState; 96 | }; 97 | if (autoSave && autoSave.item) { 98 | autoSaveLocalStorage(store, autoSave.item, autoSave.keys); 99 | } 100 | function Provider(props) { 101 | const [state, dispatch] = React.useReducer(middlewareReducer, initialState); 102 | if (!store.dispatch) { 103 | store.dispatch = async function(action) { 104 | if (typeof action === "function") { 105 | await action(dispatch, store._state); 106 | } else { 107 | dispatch(action); 108 | } 109 | }; 110 | } 111 | React.useEffect(() => { 112 | for (let i = 0; i < store.onload.length; i++) { 113 | store.onload[i](); 114 | } 115 | }, []); 116 | return ; 117 | } 118 | return { Provider, store }; 119 | } 120 | 121 | // 用于本地存储的方法 122 | export const storage = { 123 | localName: "defaultIOKey", 124 | save: (v, theKey = storage.localName) => { 125 | const theType = Object.prototype.toString.call(v); 126 | if (theType === "[object Object]") { 127 | localStorage.setItem(theKey, JSON.stringify(v)); 128 | } else if (theType === "[object String]") { 129 | localStorage.setItem(theKey, v); 130 | } else { 131 | console.warn("Warn: storage.save() param is no a Object"); 132 | } 133 | }, 134 | load: (theKey = storage.localName) => { 135 | try { 136 | const data = localStorage.getItem(theKey); 137 | if (data) { 138 | if (typeof data === "string") { 139 | return JSON.parse(data); 140 | } 141 | return data; 142 | } 143 | } catch (err) { 144 | console.warn("load last localSate error"); 145 | } 146 | }, 147 | clear: (theKey = storage.localName) => { 148 | localStorage.setItem(theKey, {}); 149 | } 150 | }; 151 | 152 | // 这里做自动保存的监听 153 | export function autoSaveLocalStorage(store, localName, needSaveKeys) { 154 | if (localName) { 155 | storage.localName = localName; 156 | } 157 | if (Object.prototype.toString.call(needSaveKeys) !== "[object Array]") { 158 | // eslint-disable-next-line 159 | console.warn("autoSaveStorageKeys: params is no a Array"); 160 | } 161 | //首次加载读取历史数据 162 | const lastLocalData = storage.load(storage.localName); 163 | if (Object.prototype.toString.call(lastLocalData) === "[object Object]") { 164 | store.onload.push(() => { 165 | store.dispatch({ 166 | type: "localStorageLoad: IO", 167 | reducer: state => { 168 | // 如果是immutable 使用toJS 169 | if (state && state.toJS) { 170 | const data = { 171 | ...state.toJS(), 172 | ...lastLocalData 173 | }; 174 | for (const key in data) { 175 | state = state.set(key, data[key]); 176 | } 177 | return state; 178 | } 179 | // 非immutable直接合并历史数据 180 | return { 181 | ...state, 182 | ...lastLocalData 183 | }; 184 | } 185 | }); 186 | }); 187 | } 188 | // 只有needSaveKeys的修改会激发IO, lastDats保存之前的记录 189 | const lastDatas = {}; 190 | needSaveKeys.forEach(v => { 191 | lastDatas[v] = undefined; 192 | }); 193 | store.subscribe(() => { 194 | const state = store.getState(); 195 | if (state && state.toJS) { 196 | //immutable 类型 197 | const nowDatas = {}; 198 | let isNeedSave = false; 199 | needSaveKeys.forEach(v => { 200 | // 监听数据和 Immutable 配合做低开销校验 201 | if (Object.prototype.toString.call(v) === "[object Array]") { 202 | nowDatas[v] = state.getIn(v); 203 | } else { 204 | nowDatas[v] = state.get(v); 205 | } 206 | if (lastDatas[v] !== nowDatas[v]) { 207 | isNeedSave = true; 208 | } 209 | lastDatas[v] = nowDatas[v]; 210 | }); 211 | if (isNeedSave) { 212 | storage.save(nowDatas); 213 | } 214 | } else { 215 | // 非immutable做浅比较判断是否需要保存 216 | const nowDatas = {}; 217 | let isNeedSave = true; 218 | needSaveKeys.forEach(v => { 219 | nowDatas[v] = state[v]; 220 | if (lastDatas[v] !== nowDatas[v]) { 221 | isNeedSave = true; 222 | } 223 | lastDatas[v] = nowDatas[v]; 224 | }); 225 | if (isNeedSave) { 226 | storage.save(nowDatas); 227 | // needSaveKeys.forEach(v => { 228 | // lastDatas[v] = nowDatas[v]; 229 | // }); 230 | } 231 | } 232 | }); 233 | } 234 | 235 | export function middlewareLog(store, lastState, nextState, action) { 236 | if (store.isDev && !action.$NOLOG) { 237 | console.log( 238 | `%c|------- redux: ${action.type} -------|`, 239 | `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;` 240 | ); 241 | if (!action.$OBJLOG && nextState && typeof nextState.toJS === "function") { 242 | const next = {}; 243 | nextState.map((d, k) => { 244 | next[k] = d; 245 | }); 246 | if (action.$LASTLOG) { 247 | const last = {}; 248 | lastState.map((d, k) => { 249 | last[k] = d; 250 | }); 251 | console.log("|--last", last); 252 | console.log("|--next", next); 253 | } else { 254 | console.log("|--", next); 255 | } 256 | } else if (action.$LASTLOG) { 257 | const last = {}; 258 | lastState.map((d, k) => { 259 | last[k] = d; 260 | }); 261 | console.log("|--last", lastState); 262 | console.log("|--next", nextState); 263 | } else { 264 | console.log("|--", nextState); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | plugins: ['react'], 4 | env: { 5 | browser: true, 6 | node: true, 7 | }, 8 | rules: { 9 | 'no-alert': 0, //禁止使用alert confirm prompt 10 | 'no-array-constructor': 2, //禁止使用数组构造器 11 | 'no-bitwise': 0, //禁止使用按位运算符 12 | 'no-caller': 0, //禁止使用arguments.caller或arguments.callee 13 | 'no-catch-shadow': 2, //禁止catch子句参数与外部作用域变量同名 14 | 'no-class-assign': 0, //禁止给类赋值 15 | 'no-cond-assign': 0, //禁止在条件表达式中使用赋值语句 16 | 'no-console': 0, //禁止使用console 17 | 'no-const-assign': 2, //禁止修改const声明的变量 18 | 'no-constant-condition': 0, //禁止在条件中使用常量表达式 if(true) 19 | 'no-continue': 0, //禁止使用continue 20 | 'no-control-regex': 0, //禁止在正则表达式中使用控制字符 21 | 'no-debugger': 2, //禁止使用debugger 22 | 'no-delete-var': 2, //不能对var声明的变量使用delete操作符 23 | 'no-div-regex': 1, //不能使用看起来像除法的正则表达式/=foo/ 24 | 'no-dupe-keys': 1, //在创建对象字面量时不允许键重复 {a:1,a:1} 25 | 'no-dupe-args': 2, //函数参数不能重复 26 | 'no-duplicate-case': 2, //switch中的case标签不能重复 27 | 'no-else-return': 1, //如果if语句里面有return,后面不能跟else语句 28 | 'no-empty': 0, //块语句中的内容不能为空 29 | 'no-empty-character-class': 1, //正则表达式中的[]内容不能为空 30 | 'no-empty-label': 0, //禁止使用空label 31 | 'no-eq-null': 2, //禁止对null使用==或!=运算符 32 | 'no-eval': 0, //禁止使用eval 33 | 'no-ex-assign': 2, //禁止给catch语句中的异常参数赋值 34 | 'no-extend-native': 0, //禁止扩展native对象 35 | 'no-extra-bind': 1, //禁止不必要的函数绑定 36 | 'no-extra-boolean-cast': 1, //禁止不必要的bool转换 37 | 'no-extra-parens': 0, //禁止非必要的括号 38 | 'no-extra-semi': 2, //禁止多余的冒号 39 | 'no-fallthrough': 1, //禁止switch穿透 40 | 'no-floating-decimal': 2, //禁止省略浮点数中的0 .5 3. 41 | 'no-func-assign': 2, //禁止重复的函数声明 42 | 'no-implicit-coercion': 0, //禁止隐式转换 43 | 'no-implied-eval': 0, //禁止使用隐式eval 44 | 'no-inline-comments': 0, //禁止行内备注 45 | 'no-inner-declarations': [0, 'functions'], //禁止在块语句中使用声明(变量或函数) 46 | 'no-invalid-regexp': 1, //禁止无效的正则表达式 47 | 'no-invalid-this': 0, //禁止无效的this,只能用在构造器,类,对象字面量 48 | 'no-irregular-whitespace': 1, //不能有不规则的空格 49 | 'no-iterator': 2, //禁止使用__iterator__ 属性 50 | 'no-label-var': 2, //label名不能与var声明的变量名相同 51 | 'no-labels': 1, //禁止标签声明 52 | 'no-lone-blocks': 1, //禁止不必要的嵌套块 53 | 'no-lonely-if': 1, //禁止else语句内只有if语句 54 | 'no-loop-func': 1, //禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以) 55 | 'no-mixed-requires': [0, false], //声明时不能混用声明类型 56 | 'no-mixed-spaces-and-tabs': [2, false], //禁止混用tab和空格 57 | // 'linebreak-style': [0, 'windows'], //换行风格 58 | 'no-multi-spaces': 1, //不能用多余的空格 59 | 'no-multi-str': 0, //字符串不能用\换行 60 | 'no-multiple-empty-lines': [1, { max: 2 }], //空行最多不能超过2行 61 | 'no-native-reassign': 1, //不能重写native对象 62 | 'no-negated-in-lhs': 2, //in 操作符的左边不能有! 63 | 'no-nested-ternary': 0, //禁止使用嵌套的三目运算 64 | 'no-new': 0, //禁止在使用new构造一个实例后不赋值 65 | 'no-new-func': 0, //禁止使用new Function 66 | 'no-new-object': 0, //禁止使用new Object() 67 | 'no-new-require': 0, //禁止使用new require 68 | 'no-new-wrappers': 1, //禁止使用new创建包装实例,new String new Boolean new Number 69 | 'no-obj-calls': 1, //不能调用内置的全局对象,比如Math() JSON() 70 | 'no-octal': 0, //禁止使用八进制数字 71 | 'no-octal-escape': 0, //禁止使用八进制转义序列 72 | 'no-param-reassign': 0, //禁止给参数重新赋值 73 | 'no-path-concat': 0, //node中不能使用__dirname或__filename做路径拼接 74 | 'no-plusplus': 0, //禁止使用++,-- 75 | 'no-process-env': 0, //禁止使用process.env 76 | 'no-process-exit': 0, //禁止使用process.exit() 77 | 'no-proto': 0, //禁止使用__proto__属性 78 | 'no-redeclare': 1, //禁止重复声明变量 79 | 'no-regex-spaces': 1, //禁止在正则表达式字面量中使用多个空格 /foo bar/ 80 | 'no-restricted-modules': 0, //如果禁用了指定模块,使用就会报错 81 | 'no-return-assign': 1, //return 语句中不能有赋值表达式 82 | 'no-script-url': 0, //禁止使用javascript:void(0) 83 | 'no-self-compare': 1, //不能比较自身 84 | 'no-sequences': 0, //禁止使用逗号运算符 85 | 'no-shadow': 1, //外部作用域中的变量不能与它所包含的作用域中的变量或参数同名 86 | 'no-shadow-restricted-names': 2, //严格模式中规定的限制标识符不能作为声明时的变量名使用 87 | 'no-spaced-func': 0, //函数调用时 函数名与()之间不能有空格 88 | 'no-sparse-arrays': 0, //禁止稀疏数组, [1,,2] 89 | 'no-sync': 0, //nodejs 禁止同步方法 90 | 'no-ternary': 0, //禁止使用三目运算符 91 | 'no-trailing-spaces': 1, //一行结束后面不要有空格 92 | 'no-this-before-super': 0, //在调用super()之前不能使用this或super 93 | 'no-throw-literal': 0, //禁止抛出字面量错误 throw "error"; 94 | 'no-undef': 2, //不能有未定义的变量 95 | 'no-undef-init': 0, //变量初始化时不能直接给它赋值为undefined 96 | 'no-undefined': 0, //不能使用undefined 97 | 'no-unexpected-multiline': 1, //避免多行表达式 98 | 'no-underscore-dangle': 0, //标识符不能以_开头或结尾 99 | 'no-unneeded-ternary': 0, //禁止不必要的嵌套 var isYes = answer === 1 ? true : false; 100 | 'no-unreachable': 1, //不能有无法执行的代码 101 | 'no-unused-expressions': 1, //禁止无用的表达式 102 | 'no-unused-vars': [0, { vars: 'all', args: 'after-used' }], //不能有声明后未被使用的变量或参数 103 | 'no-use-before-define': 0, //未定义前不能使用 104 | 'no-useless-call': 1, //禁止不必要的call和apply 105 | 'no-void': 1, //禁用void操作符 106 | 'no-var': 0, //禁用var,用let和const代替 107 | 'no-warning-comments': [ 108 | 1, 109 | { terms: ['todo', 'fixme', 'xxx'], location: 'start' }, 110 | ], //不能有警告备注 111 | 'no-with': 1, //禁用with 112 | 113 | 'array-bracket-spacing': [1, 'never'], //是否允许非空数组里面有多余的空格 114 | 'arrow-parens': 0, //箭头函数用小括号括起来 115 | 'arrow-spacing': 0, //=>的前/后括号 116 | 'accessor-pairs': 0, //在对象中使用getter/setter 117 | 'block-scoped-var': 0, //块语句中使用var 118 | 'brace-style': [1, '1tbs'], //大括号风格 119 | 'callback-return': 1, //避免多次调用回调什么的 120 | camelcase: 2, //强制驼峰法命名 121 | 'comma-dangle': [0, 'never'], //对象字面量项尾不能有逗号 122 | 'comma-spacing': 0, //逗号前后的空格 123 | 'comma-style': [0, 'last'], //逗号风格,换行时在行首还是行尾 124 | complexity: [0, 11], //循环复杂度 125 | 'computed-property-spacing': [0, 'never'], //是否允许计算后的键名什么的 126 | 'consistent-return': 0, //return 后面是否允许省略 127 | 'consistent-this': [2, 'that'], //this别名 128 | 'constructor-super': 0, //非派生类不能调用super,派生类必须调用super 129 | curly: [2, 'all'], //必须使用 if(){} 中的{} 130 | 'default-case': 2, //switch语句最后必须有default 131 | 'dot-location': 0, //对象访问符的位置,换行的时候在行首还是行尾 132 | 'dot-notation': [0, { allowKeywords: true }], //避免不必要的方括号 133 | 'eol-last': 0, //文件以单一的换行符结束 134 | eqeqeq: 2, //必须使用全等 135 | 'func-names': 0, //函数表达式必须有名字 136 | 'func-style': [0, 'declaration'], //函数风格,规定只能使用函数声明/函数表达式 137 | 'generator-star-spacing': 0, //生成器函数*的前后空格 138 | 'guard-for-in': 0, //for in循环要用if语句过滤 139 | 'handle-callback-err': 0, //nodejs 处理错误 140 | 'id-length': 0, //变量名长度 141 | // indent: [2, 4], //缩进风格 142 | 'init-declarations': 0, //声明时必须赋初值 143 | 'key-spacing': [0, { beforeColon: false, afterColon: true }], //对象字面量中冒号的前后空格 144 | 'lines-around-comment': 0, //行前/行后备注 145 | // 'max-depth': [0, 4], //嵌套块深度 146 | // 'max-len': [0, 80, 4], //字符串最大长度 147 | // 'max-nested-callbacks': [0, 2], //回调嵌套深度 148 | // 'max-params': [0, 3], //函数最多只能有3个参数 149 | // 'max-statements': [0, 10], //函数内最多有几个声明 150 | // 'new-cap': 2, //函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用 151 | 'new-parens': 2, //new时必须加小括号 152 | 'newline-after-var': 0, //变量声明后是否需要空一行 153 | 'object-curly-spacing': [0, 'never'], //大括号内是否允许不必要的空格 154 | 'object-shorthand': 0, //强制对象字面量缩写语法 155 | 'one-var': 0, //连续声明 156 | 'operator-assignment': [0, 'always'], //赋值运算符 += -=什么的 157 | 'operator-linebreak': [0, 'after'], //换行时运算符在行尾还是行首 158 | 'padded-blocks': 0, //块语句内行首行尾是否要空行 159 | 'prefer-const': 0, //首选const 160 | 'prefer-spread': 0, //首选展开运算 161 | 'prefer-reflect': 0, //首选Reflect的方法 162 | quotes: [0, 'single'], //引号类型 `` "" '' 163 | 'quote-props': [0, 'always'], //对象字面量中的属性名是否强制双引号 164 | radix: 2, //parseInt必须指定第二个参数 165 | 'id-match': 0, //命名检测 166 | 'require-yield': 0, //生成器函数必须有yield 167 | semi: [2, 'always'], //语句强制分号结尾 168 | 'semi-spacing': [0, { before: false, after: true }], //分号前后空格 169 | 'sort-vars': 0, //变量声明时排序 170 | 'space-after-keywords': [0, 'always'], //关键字后面是否要空一格 171 | 'space-before-blocks': [0, 'always'], //不以新行开始的块{前面要不要有空格 172 | 'space-before-function-paren': [0, 'always'], //函数定义时括号前面要不要有空格 173 | 'space-in-parens': [0, 'never'], //小括号里面要不要有空格 174 | 'space-infix-ops': 0, //中缀操作符周围要不要有空格 175 | 'space-return-throw-case': 0, //return throw case后面要不要加空格 176 | 'space-unary-ops': [0, { words: true, nonwords: false }], //一元运算符的前/后要不要加空格 177 | 'spaced-comment': 0, //注释风格要不要有空格什么的 178 | strict: 2, //使用严格模式 179 | 'use-isnan': 2, //禁止比较时使用NaN,只能用isNaN() 180 | 'valid-jsdoc': 0, //jsdoc规则 181 | 'valid-typeof': 2, //必须使用合法的typeof的值 182 | 'vars-on-top': 1, //var必须放在作用域顶部 183 | 'wrap-iife': [2, 'inside'], //立即执行函数表达式的小括号风格 184 | 'wrap-regex': 0, //正则表达式字面量用小括号包起来 185 | yoda: [2, 'never'], //禁止尤达条件 186 | }, 187 | }; 188 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | [English Document](README-EN.md) 2 | 3 | ## 原由 4 | 5 | react-hooks 是 react 官方新的编写推荐,我们很容易在官方的 useReducer 钩子上进行一层很简单的封装以达到和以往 react-redux \ redux-thunk \ redux-logger 类似的功能,并且大幅度简化了声明。 6 | 7 | react-hooks 的更多信息请阅读 [reactjs.org/hooks](reactjs.org/hooks); 8 | 9 | ## 先看看源码 10 | 11 | 这 70 行代码是一个完整的逻辑, 客官可以先阅读,或许后续的说明文档也就不需要阅读了。 12 | 13 | - 简易的实现了 react-redux, redux-thunk 和 redux-logger 14 | - 默认使用 reducer-in-action 的风格, 也可声明传统的 reducer 风格 15 | 16 | ```js 17 | import React from 'react'; 18 | 19 | function middlewareLog(lastState, nextState, action, isDev) { 20 | if (isDev) { 21 | console.log( 22 | `%c|------- redux: ${action.type} -------|`, 23 | `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`, 24 | ); 25 | console.log('|--last:', lastState); 26 | console.log('|--next:', nextState); 27 | } 28 | } 29 | 30 | function reducerInAction(state, action) { 31 | if (typeof action.reducer === 'function') { 32 | return action.reducer(state); 33 | } 34 | return state; 35 | } 36 | 37 | export default function createStore(params) { 38 | const { isDev, reducer, initialState, middleware } = { 39 | isDev: false, 40 | reducer: reducerInAction, 41 | initialState: {}, 42 | middleware: params.isDev ? [middlewareLog] : undefined, 43 | ...params, 44 | }; 45 | const AppContext = React.createContext(); 46 | const store = { 47 | isDev, 48 | _state: initialState, 49 | useContext: function() { 50 | return React.useContext(AppContext); 51 | }, 52 | dispatch: undefined, 53 | getState: function() { 54 | return store._state; 55 | }, 56 | initialState, 57 | }; 58 | let isCheckedMiddleware = false; 59 | const middlewareReducer = function(lastState, action) { 60 | let nextState = reducer(lastState, action); 61 | if (!isCheckedMiddleware) { 62 | if (Object.prototype.toString.call(middleware) !== '[object Array]') { 63 | throw new Error("react-hooks-redux: middleware isn't Array"); 64 | } 65 | isCheckedMiddleware = true; 66 | } 67 | for (let i = 0; i < middleware.length; i++) { 68 | const newState = middleware[i](store, lastState, nextState, action); 69 | if (newState) { 70 | nextState = newState; 71 | } 72 | } 73 | store._state = nextState; 74 | return nextState; 75 | }; 76 | 77 | const Provider = props => { 78 | const [state, dispatch] = React.useReducer(middlewareReducer, initialState); 79 | if (!store.dispatch) { 80 | store.dispatch = async function(action) { 81 | if (typeof action === 'function') { 82 | await action(dispatch, store._state); 83 | } else { 84 | dispatch(action); 85 | } 86 | }; 87 | } 88 | return ; 89 | }; 90 | return { Provider, store }; 91 | } 92 | ``` 93 | 94 | ## reducer-in-action 风格 95 | 96 | reducer-in-action 是一个 reducer 函数,这 6 行代码就是 reducer-in-action 的全部: 97 | 98 | ```js 99 | function reducerInAction(state, action) { 100 | if (typeof action.reducer === 'function') { 101 | return action.reducer(state); 102 | } 103 | return state; 104 | } 105 | ``` 106 | 107 | 它把 reducer 给简化了,放置到了每一个 action 中进行 reducer 的处理。我们再也不需要写一堆 switch,再也不需要时刻关注 action 的 type 是否和 redcer 中的 type 一致。 108 | 109 | reducer-in-action 配合 thunk 风格,可以非常简单的编写 redux,随着项目的复杂,我们只需要编写 action,会使得项目结构更清晰。 110 | 111 | ## 安装 112 | 113 | 安装 [react-hooks-redux](https://github.com/ymzuiku/react-hooks-redux), 需要 react 版本 >= 16.7 114 | 115 | ```js 116 | yarn add react-hooks-redux 117 | ``` 118 | 119 | ## 使用 120 | 121 | 我们用了不到 35 行代码就声明了一个完整的 react-redux 的例子, 拥抱 hooks。 122 | 123 | ```js 124 | import React from 'react'; 125 | import ReactHookRedux from 'react-hooks-redux'; 126 | 127 | // 通过 ReactHookRedux 获得 Provider 组件和一个 sotre 对象 128 | const { Provider, store } = ReactHookRedux({ 129 | isDev: true, // 打印日志 130 | initialState: { name: 'dog', age: 0 }, 131 | }); 132 | 133 | function actionOfAdd() { 134 | return { 135 | type: 'add the count', 136 | reducer(state) { 137 | return { ...state, age: state.age + 1 }; // 每次需要返回一个新的 state 138 | }, 139 | }; 140 | } 141 | 142 | function Button() { 143 | function handleAdd() { 144 | store.dispatch(actionOfAdd()); //dispatch 145 | } 146 | return ; 147 | } 148 | 149 | function Page() { 150 | const state = store.useContext(); 151 | return ( 152 |
153 | {state.age}
155 | ); 156 | } 157 | 158 | export default function App() { 159 | return ( 160 | 161 | 162 | 163 | ); 164 | } 165 | ``` 166 | 167 | 总结一下: 168 | 169 | - 准备工作 170 | - 使用 ReactHookRedux 创建 Provider 组件 和 store 对象 171 | - 使用 Provide r 包裹根组件 172 | - 使用 173 | - 在需要使用状态的地方 使用 store.useContext() 获取 store 中的 state 174 | - 使用 store.dispatch(action()) 派发更新 175 | 176 | 我们阅读这个小例子会发现,没有对组件进行 connect, 没有编写 reducer 函数, 这么简化设计是为了迎合 hooks, hooks 极大的简化了我们编写千篇一律的类模板,但是如果我们还是需要对组件进行 connect, 我们又回到了编写模板代码的老路。 177 | 178 | ## middleware 的编写 179 | 180 | 绝大部分情况,你不需要编写 middleware, 不过它也极其简单。middleware 是一个一维数组,数组中每个对象都是一个函数, 传入了参数并且如果返回的对象存在, 就会替换成 nextState 并且继续执行下一个 middleware。 181 | 182 | 我们可以使用 middleware 进行打印日志、编写 chrome 插件或者二次处理 state 等操作。 183 | 184 | 我们看看 middleware 的源码: 185 | 186 | ```js 187 | let nextState = reducer(lastState, action); 188 | for (let i = 0; i < middleware.length; i++) { 189 | const newState = middleware[i](lastState, nextState, action, isDev); 190 | if (newState) { 191 | nextState = newState; 192 | } 193 | } 194 | return nextState; 195 | ``` 196 | 197 | ## 性能和注意的事项 198 | 199 | 性能(和实现上)上最大的区别是,react-hooks-redux 使用 useContext 钩子代替 connect 高阶组件进行 dispatch 的派发。 200 | 201 | 在传统的 react-redux 中,如果一个组件被 connect 高阶函数进行处理,那么当 dispatch 时,这个组件相关的 mapStateToProps 函数就会被执行,并且返回新的 props 以激活组件更新。 202 | 203 | 而在 hooks 风格中,当一个组件被声明了 useContext() 时,context 相关联的对象被变更了,这个组件会进行更新。 204 | 205 | 理论上性能和 react-redux 是一致的,由于 hooks 相对于 class 有着更少的声明,所以应该会更快一些。 206 | 207 | 所以,我们有节制的使用 useContext 可以减少一些组件被 dispatch 派发更新。 208 | 209 | 如果我们需要手动控制减少更新 可以参考 [useMemo](https://reactjs.org/docs/hooks-reference.html#usememo) 钩子的使用方式进行配合。 210 | 211 | 如果不希望组件被 store.dispatch() 派发更新,仅读取数据可以使用 store.getState(), 这样也可以减少一些不必要的组件更新。 212 | 213 | 以上都是理论分析,由于此库和此文档是一个深夜的产物,并没有去做性能上的基准测试,所以有人如果愿意非常欢迎帮忙做一些基准测试。 214 | 215 | 216 | # 其他例子 217 | 218 | 随着工作的进展,完善了一些功能, 代码量也上升到了300行,有兴趣的可以去仓库看看: 219 | - subscribe 添加监听 220 | - 如使用 autoSave 约定进行 state 的缓存和读取 221 | - middlewareLog 可以打印 immutable 对象等和状态管理相关的功能 222 | 223 | ## 异步 action 并且缓存 state 到浏览器的例子 224 | 225 | ```js 226 | import React from 'react'; 227 | import ReactHookRedux, { 228 | reducerInAction, 229 | middlewareLog, 230 | } from 'react-hooks-redux'; 231 | 232 | // 通过 ReactHookRedux 获得 Provider 组件和一个 sotre 对象 233 | const { Provider, store } = ReactHookRedux({ 234 | isDev: true, // default is false 235 | initialState: { count: 0, asyncCount: 0 }, // default is {} 236 | reducer: reducerInAction, // default is reducerInAction 所以可省略 237 | middleware: [middlewareLog], // default is [middlewareLog] 所以可省略 238 | actions: {}, // default is {} 所以可省略 239 | autoSave: { 240 | item: 'localSaveKey', 241 | keys: ['user'], // 需要缓存的字段 242 | }, 243 | }); 244 | 245 | // 模拟异步操作 246 | function timeOutAdd(a) { 247 | return new Promise(cb => setTimeout(() => cb(a + 1), 500)); 248 | } 249 | 250 | const actions = { 251 | // 如果返回的是一个function,我们会把它当成类似 react-thunk 的处理方式,并且额外增加一个ownState的对象方便获取state 252 | asyncAdd: () => async (dispatch, ownState) => { 253 | const asyncCount = await timeOutAdd(ownState.asyncCount); 254 | dispatch({ 255 | type: 'asyncAdd', 256 | // if use reducerInAction, we can add reducer Function repeat reducer 257 | reducer(state) { 258 | return { 259 | ...state, 260 | asyncCount, 261 | }; 262 | }, 263 | }); 264 | }, 265 | }; 266 | 267 | function Item() { 268 | const state = store.useContext(); 269 | return
async-count: {state.asyncCount}
; 270 | } 271 | 272 | function Button() { 273 | async function handleAdd() { 274 | // 使用 async dispatch 275 | await store.dispatch(actions.asyncAdd()); 276 | } 277 | return ; 278 | } 279 | 280 | export default function App() { 281 | return ( 282 | 283 | 284 | ; 321 | } 322 | 323 | function Page() { 324 | const state = store.useContext(); 325 | // 从immutable获取对象,如果products未改变,会从堆中获取而不是重新生成新的数组 326 | const products = state.get('products'); 327 | 328 | return useCallback( 329 |
330 |
, 335 | [products], // 如果products未发生改变,不会进行进行重渲染 336 | ); 337 | } 338 | 339 | export default function App() { 340 | return ( 341 | 342 | 343 | 344 | ); 345 | } 346 | ``` 347 | 348 | ## 使用connect风格: 349 | 350 | ```js 351 | function mapStateToProps(state) { 352 | return { 353 | imgList: state.getIn(['data', 'images', 'hits']), 354 | }; 355 | } 356 | 357 | function mapDispatchToProps(dispatch) { 358 | return { 359 | asyncGetImages: async function() { 360 | return await dispatch(await actions.asyncGetImages()); 361 | }, 362 | }; 363 | } 364 | 365 | export default store.connect( 366 | mapStateToProps, 367 | mapDispatchToProps, 368 | )(Compontent); 369 | 370 | ``` 371 | 372 | 谢谢阅读。 373 | 374 | ## Licenes 375 | 376 | ``` 377 | MIT License 378 | 379 | Copyright (c) 2013-present, Facebook, Inc. 380 | 381 | Permission is hereby granted, free of charge, to any person obtaining a copy 382 | of this software and associated documentation files (the "Software"), to deal 383 | in the Software without restriction, including without limitation the rights 384 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 385 | copies of the Software, and to permit persons to whom the Software is 386 | furnished to do so, subject to the following conditions: 387 | 388 | The above copyright notice and this permission notice shall be included in all 389 | copies or substantial portions of the Software. 390 | 391 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 392 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 393 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 394 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 395 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 396 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 397 | SOFTWARE. 398 | ``` 399 | -------------------------------------------------------------------------------- /scripts/webpack.tip.js: -------------------------------------------------------------------------------- 1 | const resolve = require('path').resolve; 2 | const fs = require('fs-extra'); 3 | const webpack = require('webpack'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const FastUglifyJsPlugin = require('fast-uglifyjs-plugin'); 6 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 7 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 8 | const autoprefixer = require('autoprefixer'); 9 | const isDev = process.env.prod !== '1'; 10 | 11 | class Tip { 12 | constructor(paths, port=3300) { 13 | const rootPath = process.cwd(); 14 | this.paths = { 15 | root: rootPath, 16 | output: resolve(rootPath, 'lib'), 17 | public: resolve(rootPath, 'public'), 18 | package: resolve(rootPath, 'package.json'), 19 | entry: resolve(rootPath, 'src/index.js'), 20 | src: resolve(rootPath, 'src'), 21 | dll: resolve(rootPath, 'public/dll'), 22 | template: resolve(rootPath, 'public/index.html'), 23 | paths, 24 | }; 25 | this.isDev = isDev; 26 | if (!isDev) { 27 | fs.mkdirpSync(this.paths.output); 28 | } 29 | this.tsconfig = { 30 | compilerOptions: { 31 | target: 'es3', 32 | module: 'esnext', 33 | noImplicitAny: true, 34 | removeComments: true, 35 | preserveConstEnums: true, 36 | jsx: 'react', 37 | sourceMap: true, 38 | }, 39 | include: ['./src/**/*'], 40 | exclude: ['node_modules', '**/*.spec.ts'], 41 | }; 42 | this.stats = { 43 | errorsOnly: 'errors-only', 44 | }; 45 | this.target = { 46 | web: 'web', 47 | node: 'node', 48 | }; 49 | this.mode = { 50 | none: 'none', 51 | development: 'development', 52 | production: 'production', 53 | }; 54 | this.devtool = { 55 | none: undefined, 56 | eval: 'eval', 57 | sourceMap: 'source-map', 58 | inlineSourceMap: 'inline-source-map', 59 | cheapModuleEvalSourceMap: 'cheap-module-eval-source-map', 60 | }; 61 | this.devServer = { 62 | contentBase: this.paths.public, 63 | watchContentBase: true, 64 | port: port, 65 | host: '0.0.0.0', 66 | useLocalIp: true, 67 | // hot: true, //开启有可能不显示内容 68 | open: false, 69 | progress: false, 70 | openPage: '/', 71 | allowedHosts: [], 72 | headers: {}, 73 | disableHostCheck: false, 74 | compress: true, 75 | clientLogLevel: 'info', 76 | https: false, 77 | lazy: false, 78 | before: function(app) {}, 79 | after: function(app) {}, 80 | quiet: false, //屏蔽所有错误,控制台中输出打包的信息 81 | inline: true, //开启页面自动刷新 82 | stats: 'errors-only', 83 | noInfo: false, 84 | proxy: { 85 | // '/api-proxy': 'http://localhost:7000', 86 | }, 87 | }; 88 | this.resolve = { 89 | extensions: [ 90 | '.web.tsx', 91 | '.tsx', 92 | '.web.ts', 93 | '.ts', 94 | '.web.js', 95 | '.mjs', 96 | '.js', 97 | '.json', 98 | '.web.jsx', 99 | '.jsx', 100 | ], 101 | alias: { 102 | 'react-native': 'react-native-web', 103 | }, 104 | }; 105 | this.module = { 106 | rules: { 107 | eslint: { 108 | test: process.env.nolint ? /\.(nolint)$/ : /\.(js|jsx|mjs)$/, 109 | enforce: 'pre', 110 | use: [ 111 | { 112 | options: { 113 | // formatter: eslintFormatter, 114 | eslintPath: require.resolve('eslint'), 115 | }, 116 | loader: require.resolve('eslint-loader'), 117 | }, 118 | ], 119 | include: this.paths.src, 120 | }, 121 | tsLodaer: { 122 | test: /\.(tsx|ts)?$/, 123 | loader: 'ts-loader', 124 | // options: { 125 | // transpileOnly: true, 126 | // }, 127 | }, 128 | // .babelrc 129 | babelLoaderBuild: { 130 | test: /\.js$/, 131 | exclude: /(node_modules|bower_components)/, 132 | // include: [ 133 | // /node_modules\/react-navigation/, 134 | // ], 135 | use: { 136 | loader: 'babel-loader', 137 | options: { 138 | cacheDirectory: true, 139 | babelrc: false, 140 | comments: isDev ? false : true, 141 | presets: ['@babel/preset-env', '@babel/preset-react'], 142 | plugins: [ 143 | ['@babel/plugin-proposal-decorators', { legacy: true }], 144 | ['@babel/plugin-proposal-class-properties', { loose: true }], 145 | 'styled-components', 146 | '@babel/plugin-syntax-dynamic-import', 147 | ['transform-class-properties', { spec: true }], 148 | [ 149 | '@babel/plugin-transform-runtime', 150 | { 151 | corejs: false, 152 | helpers: true, 153 | regenerator: true, 154 | useESModules: false, 155 | }, 156 | ], 157 | [ 158 | 'module-resolver', 159 | { 160 | alias: { 161 | '^react-native$': 'react-native-web', 162 | }, 163 | }, 164 | ], 165 | ], 166 | }, 167 | }, 168 | }, 169 | babelLoaderDll: { 170 | test: /\.js$/, 171 | exclude: /(node_modules|bower_components)/, 172 | include: [ 173 | /node_modules\/react-native-/, 174 | /node_modules\/react-native-web/, 175 | ], 176 | use: { 177 | loader: 'babel-loader', 178 | options: { 179 | cacheDirectory: true, 180 | babelrc: false, 181 | comments: true, 182 | presets: ['@babel/preset-env', '@babel/preset-react'], 183 | plugins: [ 184 | ['@babel/plugin-proposal-decorators', { legacy: true }], 185 | ['@babel/plugin-proposal-class-properties', { loose: true }], 186 | ['transform-class-properties', { spec: true }], 187 | [ 188 | '@babel/plugin-transform-runtime', 189 | { 190 | corejs: false, 191 | helpers: true, 192 | regenerator: true, 193 | useESModules: false, 194 | }, 195 | ], 196 | [ 197 | 'module-resolver', 198 | { 199 | alias: { 200 | '^react-native$': 'react-native-web', 201 | }, 202 | }, 203 | ], 204 | ], 205 | }, 206 | }, 207 | }, 208 | sourceMapLoader: { 209 | enforce: 'pre', 210 | test: /\.(js|jsx)$/, 211 | loader: 'source-map-loader', 212 | }, 213 | urlLoader: { 214 | test: /\.(png|svg|jpg|gif|pdf)$/, 215 | use: [ 216 | { 217 | loader: 'url-loader', 218 | options: { 219 | limit: 8192, 220 | }, 221 | }, 222 | ], 223 | }, 224 | fileLoader: { 225 | test: /\.(woff|woff2|eot|ttf|otf)$/, 226 | use: ['file-loader'], 227 | }, 228 | cssLoader: { 229 | test: /\.css$/, 230 | use: [ 231 | { loader: 'css-loader' }, 232 | { 233 | loader: 'postcss-loader', 234 | options: { 235 | sourceMap: true, 236 | ident: 'postcss', 237 | plugins: () => [ 238 | require('postcss-flexbugs-fixes'), 239 | autoprefixer({ 240 | browsers: [ 241 | '>1%', 242 | 'last 4 versions', 243 | 'Firefox ESR', 244 | 'not ie < 9', // React doesn't support IE8 anyway 245 | ], 246 | flexbox: 'no-2009', 247 | }), 248 | ], 249 | }, 250 | }, 251 | ], 252 | }, 253 | stylusLoader: { 254 | test: /\.styl$/, 255 | use: [ 256 | { 257 | loader: 'style-loader', 258 | }, 259 | { 260 | loader: 'stylus-loader', 261 | options: { 262 | strictMath: true, 263 | noIeCompat: true, 264 | sourceMap: true, 265 | }, 266 | }, 267 | { 268 | loader: 'postcss-loader', 269 | options: { 270 | sourceMap: true, 271 | ident: 'postcss', 272 | plugins: () => [ 273 | require('postcss-flexbugs-fixes'), 274 | autoprefixer({ 275 | browsers: [ 276 | '>1%', 277 | 'last 4 versions', 278 | 'Firefox ESR', 279 | 'not ie < 9', // React doesn't support IE8 anyway 280 | ], 281 | flexbox: 'no-2009', 282 | }), 283 | ], 284 | }, 285 | }, 286 | ], 287 | }, 288 | lessLoader: { 289 | test: /\.less$/, 290 | use: [ 291 | { 292 | loader: 'style-loader', 293 | }, 294 | { 295 | loader: 'less-loader', 296 | options: { 297 | strictMath: true, 298 | noIeCompat: true, 299 | sourceMap: true, 300 | }, 301 | }, 302 | { 303 | loader: 'postcss-loader', 304 | options: { 305 | sourceMap: true, 306 | ident: 'postcss', 307 | plugins: () => [ 308 | require('postcss-flexbugs-fixes'), 309 | autoprefixer({ 310 | browsers: [ 311 | '>1%', 312 | 'last 4 versions', 313 | 'Firefox ESR', 314 | 'not ie < 9', // React doesn't support IE8 anyway 315 | ], 316 | flexbox: 'no-2009', 317 | }), 318 | ], 319 | }, 320 | }, 321 | ], 322 | }, 323 | }, 324 | }; 325 | this.plugins = { 326 | HashedModuleIdsPlugin: new webpack.HashedModuleIdsPlugin(), 327 | ProvidePlugin: new webpack.ProvidePlugin({ 328 | $: 'jquery', 329 | _: 'lodash', 330 | }), 331 | HtmlWebpackPlugin: new HtmlWebpackPlugin({ 332 | template: this.paths.template, 333 | minify: true, 334 | // minify: { 335 | // removeAttributeQuotes: false, // 移除属性的引号 336 | // }, 337 | }), 338 | null: new webpack.DefinePlugin({ 339 | __DEV__: false, 340 | }), 341 | DefinePlugin: new webpack.DefinePlugin({ 342 | __DEV__: false, 343 | }), 344 | HotModuleReplacementPlugin: new webpack.HotModuleReplacementPlugin(), 345 | FastUglifyJsPluginProd: new FastUglifyJsPlugin({ 346 | compress: { 347 | warnings: false, 348 | }, 349 | debug: false, 350 | cache: false, 351 | sourceMap: false, 352 | }), 353 | FastUglifyJsPluginDev: new FastUglifyJsPlugin({ 354 | compress: false, 355 | debug: true, 356 | cache: true, 357 | sourceMap: true, 358 | cacheFolder: resolve(this.paths.root, './node_modules/.cache'), 359 | }), 360 | FastUglifyJsPluginDll: new FastUglifyJsPlugin({ 361 | compress: { 362 | warnings: false, 363 | }, 364 | debug: true, 365 | cache: false, 366 | sourceMap: true, 367 | cacheFolder: resolve(this.paths.root, './node_modules/.cache'), 368 | }), 369 | CleanWebpackPlugin: new CleanWebpackPlugin(['*'], { 370 | root: this.paths.output, 371 | exclude: ['video'], 372 | verbose: true, 373 | dry: false, 374 | }), 375 | DllReferencePlugin: new webpack.DllReferencePlugin({ 376 | manifest: resolve(this.paths.dll, 'dll-manifest.json'), 377 | }), 378 | DllPlugin: new webpack.DllPlugin({ 379 | path: resolve(this.paths.dll, 'dll-manifest.json'), 380 | name: 'dll_library', 381 | }), 382 | CopyWebpackPlugin: new CopyWebpackPlugin([ 383 | { 384 | from: this.paths.public, 385 | to: this.paths.output, 386 | }, 387 | ]), 388 | }; 389 | } 390 | } 391 | 392 | module.exports = function(...args) { 393 | return new Tip(...args); 394 | }; 395 | -------------------------------------------------------------------------------- /lib/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack:///webpack/universalModuleDefinition","webpack:///index.js","webpack:///webpack/bootstrap","webpack:///./src/index.js","webpack:///external \"@babel/runtime/helpers/interopRequireDefault\"","webpack:///external \"@babel/runtime/regenerator\"","webpack:///external \"@babel/runtime/helpers/asyncToGenerator\"","webpack:///external \"@babel/runtime/helpers/slicedToArray\"","webpack:///external \"@babel/runtime/helpers/extends\"","webpack:///external \"@babel/runtime/helpers/objectSpread\"","webpack:///external \"react\""],"names":["root","factory","exports","module","define","amd","a","i","window","modules","installedModules","__webpack_require__","moduleId","l","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","_interopRequireDefault","default","options","arguments","length","undefined","defalutOptions","_defalutOptions$optio","_objectSpread2","isDev","reducer","initialState","middleware","autoSave","AppContext","React","createContext","store","_state","useContext","subscribe","dispatch","getState","onload","connect","mapStateToProps","mapDispatchToProps","Comp","props","imm","stateToProps","dispatchProps","memoList","k","push","useMemo","_react","createElement","_extends2","isCheckedMiddleware","middlewareReducer","lastState","action","nextState","toString","Error","newState","runSubscribes","item","autoSaveLocalStorage","keys","Provider","_React$useReducer","useReducer","_React$useReducer2","_slicedToArray2","state","_ref","_asyncToGenerator2","_regenerator","mark","_callee","wrap","_context","prev","next","stop","this","_x","apply","useEffect","middlewareLog","storage","subscribeCache","subscribeNum","fn","localName","save","v","theKey","theType","localStorage","setItem","JSON","stringify","console","warn","load","data","getItem","parse","err","clear","needSaveKeys","lastLocalData","type","toJS","set","lastDatas","forEach","nowDatas","isNeedSave","getIn","$NOLOG","log","concat","$OBJLOG","map","$LASTLOG","last","require"],"mappings":"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","file":"index.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse {\n\t\tvar a = factory();\n\t\tfor(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];\n\t}\n})(window, function() {\nreturn ","(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse {\n\t\tvar a = factory();\n\t\tfor(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];\n\t}\n})(window, function() {\nreturn /******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// define __esModule on exports\n/******/ \t__webpack_require__.r = function(exports) {\n/******/ \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n/******/ \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n/******/ \t\t}\n/******/ \t\tObject.defineProperty(exports, '__esModule', { value: true });\n/******/ \t};\n/******/\n/******/ \t// create a fake namespace object\n/******/ \t// mode & 1: value is a module id, require it\n/******/ \t// mode & 2: merge all properties of value into the ns\n/******/ \t// mode & 4: return value when already ns object\n/******/ \t// mode & 8|1: behave like require\n/******/ \t__webpack_require__.t = function(value, mode) {\n/******/ \t\tif(mode & 1) value = __webpack_require__(value);\n/******/ \t\tif(mode & 8) return value;\n/******/ \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n/******/ \t\tvar ns = Object.create(null);\n/******/ \t\t__webpack_require__.r(ns);\n/******/ \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n/******/ \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n/******/ \t\treturn ns;\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = 0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/*!**********************!*\\\n !*** ./src/index.js ***!\n \\**********************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports, __webpack_require__) {\n\n\"use strict\";\n\n\nvar _interopRequireDefault = __webpack_require__(/*! @babel/runtime/helpers/interopRequireDefault */ 1);\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = createStore;\nexports.autoSaveLocalStorage = autoSaveLocalStorage;\nexports.middlewareLog = middlewareLog;\nexports.storage = void 0;\n\nvar _regenerator = _interopRequireDefault(__webpack_require__(/*! @babel/runtime/regenerator */ 2));\n\nvar _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(/*! @babel/runtime/helpers/asyncToGenerator */ 3));\n\nvar _slicedToArray2 = _interopRequireDefault(__webpack_require__(/*! @babel/runtime/helpers/slicedToArray */ 4));\n\nvar _extends2 = _interopRequireDefault(__webpack_require__(/*! @babel/runtime/helpers/extends */ 5));\n\nvar _objectSpread2 = _interopRequireDefault(__webpack_require__(/*! @babel/runtime/helpers/objectSpread */ 6));\n\nvar _react = _interopRequireDefault(__webpack_require__(/*! react */ 7));\n\nfunction reducerInAction(state, action) {\n if (typeof action.reducer === \"function\") {\n return action.reducer(state);\n }\n\n return state;\n}\n\nvar subscribeCache = {};\nvar subscribeNum = 0;\n\nfunction subscribe(fn) {\n if (typeof fn !== \"function\") {\n throw new Error(\"react-hooks-redux: subscribe params need a function\");\n }\n\n subscribeNum++;\n subscribeCache[subscribeNum] = fn;\n\n function unSubscribe() {\n delete subscribeCache[subscribeNum];\n }\n\n return unSubscribe;\n}\n\nfunction runSubscribes(state, action) {\n for (var k in subscribeCache) {\n subscribeCache[k](state, action);\n }\n}\n\nvar defalutOptions = {\n isDev: false,\n reducer: reducerInAction,\n initialState: {},\n middleware: [middlewareLog],\n autoSave: {\n item: undefined,\n keys: []\n }\n};\n\nfunction createStore() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defalutOptions;\n\n var _defalutOptions$optio = (0, _objectSpread2.default)({}, defalutOptions, options),\n isDev = _defalutOptions$optio.isDev,\n reducer = _defalutOptions$optio.reducer,\n initialState = _defalutOptions$optio.initialState,\n middleware = _defalutOptions$optio.middleware,\n autoSave = _defalutOptions$optio.autoSave;\n\n var AppContext = _react.default.createContext();\n\n var store = {\n isDev: isDev,\n _state: initialState,\n useContext: function useContext() {\n return _react.default.useContext(AppContext);\n },\n subscribe: subscribe,\n dispatch: undefined,\n getState: function getState() {\n return store._state;\n },\n onload: [],\n initialState: initialState,\n connect: function connect(mapStateToProps, mapDispatchToProps) {\n return function (Comp) {\n return function (props) {\n var imm = store.useContext();\n var stateToProps = mapStateToProps(imm);\n var dispatchProps = mapDispatchToProps(store.dispatch, imm);\n var memoList = [];\n\n for (var k in stateToProps) {\n memoList.push(stateToProps[k]);\n }\n\n function render() {\n return _react.default.createElement(Comp, (0, _extends2.default)({}, stateToProps, dispatchProps, props));\n }\n\n return _react.default.useMemo(render, memoList);\n };\n };\n }\n };\n var isCheckedMiddleware = false;\n\n var middlewareReducer = function middlewareReducer(lastState, action) {\n if (!action) {\n return lastState;\n }\n\n var nextState = reducer(lastState, action);\n\n if (!isCheckedMiddleware) {\n if (Object.prototype.toString.call(middleware) !== \"[object Array]\") {\n throw new Error(\"react-hooks-redux: middleware isn't Array\");\n }\n\n isCheckedMiddleware = true;\n }\n\n for (var i = 0; i < middleware.length; i++) {\n var newState = middleware[i](store, lastState, nextState, action);\n\n if (newState) {\n nextState = newState;\n }\n }\n\n store._state = nextState;\n runSubscribes(nextState, action);\n return nextState;\n };\n\n if (autoSave && autoSave.item) {\n autoSaveLocalStorage(store, autoSave.item, autoSave.keys);\n }\n\n function Provider(props) {\n var _React$useReducer = _react.default.useReducer(middlewareReducer, initialState),\n _React$useReducer2 = (0, _slicedToArray2.default)(_React$useReducer, 2),\n state = _React$useReducer2[0],\n dispatch = _React$useReducer2[1];\n\n if (!store.dispatch) {\n store.dispatch =\n /*#__PURE__*/\n function () {\n var _ref = (0, _asyncToGenerator2.default)(\n /*#__PURE__*/\n _regenerator.default.mark(function _callee(action) {\n return _regenerator.default.wrap(function _callee$(_context) {\n while (1) {\n switch (_context.prev = _context.next) {\n case 0:\n if (!(typeof action === \"function\")) {\n _context.next = 5;\n break;\n }\n\n _context.next = 3;\n return action(dispatch, store._state);\n\n case 3:\n _context.next = 6;\n break;\n\n case 5:\n dispatch(action);\n\n case 6:\n case \"end\":\n return _context.stop();\n }\n }\n }, _callee, this);\n }));\n\n return function (_x) {\n return _ref.apply(this, arguments);\n };\n }();\n }\n\n _react.default.useEffect(function () {\n for (var i = 0; i < store.onload.length; i++) {\n store.onload[i]();\n }\n }, []);\n\n return _react.default.createElement(AppContext.Provider, (0, _extends2.default)({}, props, {\n value: state\n }));\n }\n\n return {\n Provider: Provider,\n store: store\n };\n} // 用于本地存储的方法\n\n\nvar storage = {\n localName: \"defaultIOKey\",\n save: function save(v) {\n var theKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : storage.localName;\n var theType = Object.prototype.toString.call(v);\n\n if (theType === \"[object Object]\") {\n localStorage.setItem(theKey, JSON.stringify(v));\n } else if (theType === \"[object String]\") {\n localStorage.setItem(theKey, v);\n } else {\n console.warn(\"Warn: storage.save() param is no a Object\");\n }\n },\n load: function load() {\n var theKey = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : storage.localName;\n\n try {\n var data = localStorage.getItem(theKey);\n\n if (data) {\n if (typeof data === \"string\") {\n return JSON.parse(data);\n }\n\n return data;\n }\n } catch (err) {\n console.warn(\"load last localSate error\");\n }\n },\n clear: function clear() {\n var theKey = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : storage.localName;\n localStorage.setItem(theKey, {});\n }\n}; // 这里做自动保存的监听\n\nexports.storage = storage;\n\nfunction autoSaveLocalStorage(store, localName, needSaveKeys) {\n if (localName) {\n storage.localName = localName;\n }\n\n if (Object.prototype.toString.call(needSaveKeys) !== \"[object Array]\") {\n // eslint-disable-next-line\n console.warn(\"autoSaveStorageKeys: params is no a Array\");\n } //首次加载读取历史数据\n\n\n var lastLocalData = storage.load(storage.localName);\n\n if (Object.prototype.toString.call(lastLocalData) === \"[object Object]\") {\n store.onload.push(function () {\n store.dispatch({\n type: \"localStorageLoad: IO\",\n reducer: function reducer(state) {\n // 如果是immutable 使用toJS\n if (state && state.toJS) {\n var data = (0, _objectSpread2.default)({}, state.toJS(), lastLocalData);\n\n for (var key in data) {\n state = state.set(key, data[key]);\n }\n\n return state;\n } // 非immutable直接合并历史数据\n\n\n return (0, _objectSpread2.default)({}, state, lastLocalData);\n }\n });\n });\n } // 只有needSaveKeys的修改会激发IO, lastDats保存之前的记录\n\n\n var lastDatas = {};\n needSaveKeys.forEach(function (v) {\n lastDatas[v] = undefined;\n });\n store.subscribe(function () {\n var state = store.getState();\n\n if (state && state.toJS) {\n //immutable 类型\n var nowDatas = {};\n var isNeedSave = false;\n needSaveKeys.forEach(function (v) {\n // 监听数据和 Immutable 配合做低开销校验\n if (Object.prototype.toString.call(v) === \"[object Array]\") {\n nowDatas[v] = state.getIn(v);\n } else {\n nowDatas[v] = state.get(v);\n }\n\n if (lastDatas[v] !== nowDatas[v]) {\n isNeedSave = true;\n }\n\n lastDatas[v] = nowDatas[v];\n });\n\n if (isNeedSave) {\n storage.save(nowDatas);\n }\n } else {\n // 非immutable做浅比较判断是否需要保存\n var _nowDatas = {};\n var _isNeedSave = true;\n needSaveKeys.forEach(function (v) {\n _nowDatas[v] = state[v];\n\n if (lastDatas[v] !== _nowDatas[v]) {\n _isNeedSave = true;\n }\n\n lastDatas[v] = _nowDatas[v];\n });\n\n if (_isNeedSave) {\n storage.save(_nowDatas); // needSaveKeys.forEach(v => {\n // lastDatas[v] = nowDatas[v];\n // });\n }\n }\n });\n}\n\nfunction middlewareLog(store, lastState, nextState, action) {\n if (store.isDev && !action.$NOLOG) {\n console.log(\"%c|------- redux: \".concat(action.type, \" -------|\"), \"background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;\");\n\n if (!action.$OBJLOG && nextState && typeof nextState.toJS === \"function\") {\n var next = {};\n nextState.map(function (d, k) {\n next[k] = d;\n });\n\n if (action.$LASTLOG) {\n var last = {};\n lastState.map(function (d, k) {\n last[k] = d;\n });\n console.log(\"|--last\", last);\n console.log(\"|--next\", next);\n } else {\n console.log(\"|--\", next);\n }\n } else if (action.$LASTLOG) {\n var _last = {};\n lastState.map(function (d, k) {\n _last[k] = d;\n });\n console.log(\"|--last\", lastState);\n console.log(\"|--next\", nextState);\n } else {\n console.log(\"|--\", nextState);\n }\n }\n}\n\n/***/ }),\n/* 1 */\n/*!***************************************************************!*\\\n !*** external \"@babel/runtime/helpers/interopRequireDefault\" ***!\n \\***************************************************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"@babel/runtime/helpers/interopRequireDefault\");\n\n/***/ }),\n/* 2 */\n/*!*********************************************!*\\\n !*** external \"@babel/runtime/regenerator\" ***!\n \\*********************************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"@babel/runtime/regenerator\");\n\n/***/ }),\n/* 3 */\n/*!**********************************************************!*\\\n !*** external \"@babel/runtime/helpers/asyncToGenerator\" ***!\n \\**********************************************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"@babel/runtime/helpers/asyncToGenerator\");\n\n/***/ }),\n/* 4 */\n/*!*******************************************************!*\\\n !*** external \"@babel/runtime/helpers/slicedToArray\" ***!\n \\*******************************************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"@babel/runtime/helpers/slicedToArray\");\n\n/***/ }),\n/* 5 */\n/*!*************************************************!*\\\n !*** external \"@babel/runtime/helpers/extends\" ***!\n \\*************************************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"@babel/runtime/helpers/extends\");\n\n/***/ }),\n/* 6 */\n/*!******************************************************!*\\\n !*** external \"@babel/runtime/helpers/objectSpread\" ***!\n \\******************************************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"@babel/runtime/helpers/objectSpread\");\n\n/***/ }),\n/* 7 */\n/*!************************!*\\\n !*** external \"react\" ***!\n \\************************/\n/*! no static exports found */\n/*! all exports used */\n/*! ModuleConcatenation bailout: Module is not an ECMAScript module */\n/***/ (function(module, exports) {\n\nmodule.exports = require(\"react\");\n\n/***/ })\n/******/ ]);\n});"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n","import React from \"react\";\n\nfunction reducerInAction(state, action) {\n if (typeof action.reducer === \"function\") {\n return action.reducer(state);\n }\n return state;\n}\n\nconst subscribeCache = {};\nlet subscribeNum = 0;\nfunction subscribe(fn) {\n if (typeof fn !== \"function\") {\n throw new Error(\"react-hooks-redux: subscribe params need a function\");\n }\n subscribeNum++;\n subscribeCache[subscribeNum] = fn;\n function unSubscribe() {\n delete subscribeCache[subscribeNum];\n }\n return unSubscribe;\n}\n\nfunction runSubscribes(state, action) {\n for (const k in subscribeCache) {\n subscribeCache[k](state, action);\n }\n}\n\nconst defalutOptions = {\n isDev: false,\n reducer: reducerInAction,\n initialState: {},\n middleware: [middlewareLog],\n autoSave: { item: undefined, keys: [] }\n};\n\nexport default function createStore(options = defalutOptions) {\n const { isDev, reducer, initialState, middleware, autoSave } = {\n ...defalutOptions,\n ...options\n };\n const AppContext = React.createContext();\n const store = {\n isDev,\n _state: initialState,\n useContext: function() {\n return React.useContext(AppContext);\n },\n subscribe,\n dispatch: undefined,\n getState: function() {\n return store._state;\n },\n onload: [],\n initialState,\n connect: function(mapStateToProps, mapDispatchToProps) {\n return function(Comp) {\n return function(props) {\n const imm = store.useContext();\n const stateToProps = mapStateToProps(imm);\n const dispatchProps = mapDispatchToProps(store.dispatch, imm);\n const memoList = [];\n for (const k in stateToProps) {\n memoList.push(stateToProps[k]);\n }\n function render() {\n return ;\n }\n return React.useMemo(render, memoList);\n };\n };\n }\n };\n let isCheckedMiddleware = false;\n const middlewareReducer = function(lastState, action) {\n if (!action) {\n return lastState;\n }\n let nextState = reducer(lastState, action);\n if (!isCheckedMiddleware) {\n if (Object.prototype.toString.call(middleware) !== \"[object Array]\") {\n throw new Error(\"react-hooks-redux: middleware isn't Array\");\n }\n isCheckedMiddleware = true;\n }\n for (let i = 0; i < middleware.length; i++) {\n const newState = middleware[i](store, lastState, nextState, action);\n if (newState) {\n nextState = newState;\n }\n }\n store._state = nextState;\n runSubscribes(nextState, action);\n return nextState;\n };\n if (autoSave && autoSave.item) {\n autoSaveLocalStorage(store, autoSave.item, autoSave.keys);\n }\n function Provider(props) {\n const [state, dispatch] = React.useReducer(middlewareReducer, initialState);\n if (!store.dispatch) {\n store.dispatch = async function(action) {\n if (typeof action === \"function\") {\n await action(dispatch, store._state);\n } else {\n dispatch(action);\n }\n };\n }\n React.useEffect(() => {\n for (let i = 0; i < store.onload.length; i++) {\n store.onload[i]();\n }\n }, []);\n return ;\n }\n return { Provider, store };\n}\n\n// 用于本地存储的方法\nexport const storage = {\n localName: \"defaultIOKey\",\n save: (v, theKey = storage.localName) => {\n const theType = Object.prototype.toString.call(v);\n if (theType === \"[object Object]\") {\n localStorage.setItem(theKey, JSON.stringify(v));\n } else if (theType === \"[object String]\") {\n localStorage.setItem(theKey, v);\n } else {\n console.warn(\"Warn: storage.save() param is no a Object\");\n }\n },\n load: (theKey = storage.localName) => {\n try {\n const data = localStorage.getItem(theKey);\n if (data) {\n if (typeof data === \"string\") {\n return JSON.parse(data);\n }\n return data;\n }\n } catch (err) {\n console.warn(\"load last localSate error\");\n }\n },\n clear: (theKey = storage.localName) => {\n localStorage.setItem(theKey, {});\n }\n};\n\n// 这里做自动保存的监听\nexport function autoSaveLocalStorage(store, localName, needSaveKeys) {\n if (localName) {\n storage.localName = localName;\n }\n if (Object.prototype.toString.call(needSaveKeys) !== \"[object Array]\") {\n // eslint-disable-next-line\n console.warn(\"autoSaveStorageKeys: params is no a Array\");\n }\n //首次加载读取历史数据\n const lastLocalData = storage.load(storage.localName);\n if (Object.prototype.toString.call(lastLocalData) === \"[object Object]\") {\n store.onload.push(() => {\n store.dispatch({\n type: \"localStorageLoad: IO\",\n reducer: state => {\n // 如果是immutable 使用toJS\n if (state && state.toJS) {\n const data = {\n ...state.toJS(),\n ...lastLocalData\n };\n for (const key in data) {\n state = state.set(key, data[key]);\n }\n return state;\n }\n // 非immutable直接合并历史数据\n return {\n ...state,\n ...lastLocalData\n };\n }\n });\n });\n }\n // 只有needSaveKeys的修改会激发IO, lastDats保存之前的记录\n const lastDatas = {};\n needSaveKeys.forEach(v => {\n lastDatas[v] = undefined;\n });\n store.subscribe(() => {\n const state = store.getState();\n if (state && state.toJS) {\n //immutable 类型\n const nowDatas = {};\n let isNeedSave = false;\n needSaveKeys.forEach(v => {\n // 监听数据和 Immutable 配合做低开销校验\n if (Object.prototype.toString.call(v) === \"[object Array]\") {\n nowDatas[v] = state.getIn(v);\n } else {\n nowDatas[v] = state.get(v);\n }\n if (lastDatas[v] !== nowDatas[v]) {\n isNeedSave = true;\n }\n lastDatas[v] = nowDatas[v];\n });\n if (isNeedSave) {\n storage.save(nowDatas);\n }\n } else {\n // 非immutable做浅比较判断是否需要保存\n const nowDatas = {};\n let isNeedSave = true;\n needSaveKeys.forEach(v => {\n nowDatas[v] = state[v];\n if (lastDatas[v] !== nowDatas[v]) {\n isNeedSave = true;\n }\n lastDatas[v] = nowDatas[v];\n });\n if (isNeedSave) {\n storage.save(nowDatas);\n // needSaveKeys.forEach(v => {\n // lastDatas[v] = nowDatas[v];\n // });\n }\n }\n });\n}\n\nexport function middlewareLog(store, lastState, nextState, action) {\n if (store.isDev && !action.$NOLOG) {\n console.log(\n `%c|------- redux: ${action.type} -------|`,\n `background: rgb(70, 70, 70); color: rgb(240, 235, 200); width:100%;`\n );\n if (!action.$OBJLOG && nextState && typeof nextState.toJS === \"function\") {\n const next = {};\n nextState.map((d, k) => {\n next[k] = d;\n });\n if (action.$LASTLOG) {\n const last = {};\n lastState.map((d, k) => {\n last[k] = d;\n });\n console.log(\"|--last\", last);\n console.log(\"|--next\", next);\n } else {\n console.log(\"|--\", next);\n }\n } else if (action.$LASTLOG) {\n const last = {};\n lastState.map((d, k) => {\n last[k] = d;\n });\n console.log(\"|--last\", lastState);\n console.log(\"|--next\", nextState);\n } else {\n console.log(\"|--\", nextState);\n }\n }\n}\n","module.exports = require(\"@babel/runtime/helpers/interopRequireDefault\");","module.exports = require(\"@babel/runtime/regenerator\");","module.exports = require(\"@babel/runtime/helpers/asyncToGenerator\");","module.exports = require(\"@babel/runtime/helpers/slicedToArray\");","module.exports = require(\"@babel/runtime/helpers/extends\");","module.exports = require(\"@babel/runtime/helpers/objectSpread\");","module.exports = require(\"react\");"],"sourceRoot":""} --------------------------------------------------------------------------------