├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ └── github-actions-demo.yml ├── .gitignore ├── .prettierrc ├── README.md ├── babel.config.js ├── jest.config.js ├── package.json ├── scripts └── release.ts ├── src ├── constants.ts ├── definitions.ts ├── index.ts ├── loadingPlugin.ts └── plugin.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * plugins和extends的区别 3 | * 如果eslint里没有的规则需要plugin做拓展 4 | */ 5 | module.exports = { 6 | /** 7 | * node或者浏览器中的全局变量很多,如果我们一个个进行声明显得繁琐, 8 | * 因此就需要用到我们的env,这是对环境定义的一组全局变量的预设 9 | */ 10 | env: { 11 | browser: true, 12 | commonjs: true, 13 | es6: true, 14 | node: true, 15 | jest: true, 16 | }, 17 | parser: '@typescript-eslint/parser', 18 | /** 19 | * 插件是一个 npm 包,通常输出规则。一些插件也可以输出一个或多个命名的配置。要确保这个包安装在 ESLint 能请求到的目录下。 20 | * plugins属性值可以省略包名的前缀eslint-plugin-。extends属性值可以由以下组成: 21 | * plugin:包名 (省略了前缀,比如,react)配置名称 (比如recommended) 22 | * 插件一个主要的作用就是补充规则,比如eslint:recommended中没有有关react的规则,则需要另外导入规则插件eslint-plugin-react 23 | */ 24 | plugins: ['react', 'babel', '@typescript-eslint/eslint-plugin'], 25 | /** 26 | * eslint:开头的ESLint官方扩展,有两个:eslint:recommended(推荐规范)和eslint:all(所有规范)。 27 | * plugin:开头的扩展是插件类型扩展 28 | * eslint-config:开头的来自npm包,使用时可以省略eslint-config- 29 | * @:开头的扩展和eslint-config一样,是在npm包上面加了一层作用域scope 30 | * 需要注意的是:多个扩展中有相同的规则,以后面引入的扩展中规则为准。 31 | */ 32 | extends: [ 33 | 'airbnb', 34 | 'plugin:react/recommended', 35 | 'plugin:prettier/recommended', 36 | 'plugin:react-hooks/recommended', 37 | ], 38 | parserOptions: { 39 | sourceType: 'module', 40 | ecmaFeatures: { 41 | experimentalObjectRestSpread: true, 42 | jsx: true, // 启用 JSX 43 | }, 44 | }, 45 | globals: { 46 | describe: false, 47 | it: false, 48 | expect: false, 49 | jest: false, 50 | afterEach: false, 51 | beforeEach: false, 52 | }, 53 | rules: { 54 | 'prettier/prettier': ['error', { singleQuote: true }], 55 | 'react/prop-types': 0, 56 | 'react/display-name': 0, 57 | 'react/jsx-no-target-blank': 0, // 允许 target 等于 blank 58 | 'react/jsx-key': 1, // jsx 中的遍历,需要加 key 属性,没有会提示警告 59 | 'react/no-find-dom-node': 0, 60 | indent: 0, 61 | 'jsx-quotes': [2, 'prefer-double'], // jsx 属性统一使用双引号 62 | 'max-len': [1, { code: 140 }], // 渐进式调整,先设置最大长度为 140,同时只是警告 63 | 'no-mixed-spaces-and-tabs': 2, 64 | 'no-tabs': 2, 65 | 'no-trailing-spaces': 2, // 语句尾部不能出现空格 66 | quotes: [2, 'single'], // 统一使用单引号 67 | 'space-before-blocks': 2, // if 和函数等,大括号前需要空格 68 | 'space-in-parens': 2, // 括号内前后不加空格 69 | 'space-infix-ops': 2, // 中缀(二元)操作符前后加空格 70 | 'spaced-comment': 2, // 注释双斜杠后保留一个空格 71 | '@typescript-eslint/explicit-function-return-type': 0, 72 | '@typescript-eslint/no-explicit-any': 0, 73 | '@typescript-eslint/no-non-null-assertion': 0, 74 | '@typescript-eslint/ban-ts-ignore': 0, 75 | '@typescript-eslint/interface-name-prefix': 0, 76 | '@typescript-eslint/no-use-before-define': 0, 77 | 'react-hooks/rules-of-hooks': 'error', 78 | 'react-hooks/exhaustive-deps': 'warn', 79 | '@typescript-eslint/explicit-module-boundary-types': 0, 80 | '@typescript-eslint/ban-ts-comment': 0, 81 | 'consistent-return': 0, 82 | 'no-underscore-dangle': 0, 83 | 'import/prefer-default-export': 0, 84 | 'import/extensions': 0, 85 | 'import/no-unresolved': 0, 86 | camelcase: 0, 87 | 'no-plusplus': 0, 88 | 'no-param-reassign': 0, 89 | 'no-console': 0, 90 | 'import/no-extraneous-dependencies': 0, 91 | 'import/no-dynamic-require': 0, 92 | 'no-shadow': 0, 93 | 'global-require': 0, 94 | 'no-unused-expressions': 0, 95 | 'no-unused-vars': 0, 96 | 'no-restricted-syntax': 0, 97 | 'class-methods-use-this': 0, 98 | }, 99 | }; 100 | -------------------------------------------------------------------------------- /.github/workflows/github-actions-demo.yml: -------------------------------------------------------------------------------- 1 | name: learn-github-actions 2 | 3 | run-name: ${{ github.actor }} is learning Github Actions 4 | 5 | on: [push] 6 | 7 | jobs: 8 | check-bats-version: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: '20' 17 | - run: npm install -g bats 18 | - run: bats -v 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom ignore 2 | /build 3 | /dist 4 | /lib 5 | /esm 6 | /types 7 | .vscode 8 | yarn.lock 9 | 10 | # General 11 | .DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | .vscode/* 35 | !.vscode/settings.json 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | !.vscode/extensions.json 39 | *.code-workspace 40 | 41 | # Local History for Visual Studio Code 42 | .history/ 43 | 44 | # Logs 45 | logs 46 | *.log 47 | npm-debug.log* 48 | yarn-debug.log* 49 | yarn-error.log* 50 | lerna-debug.log* 51 | 52 | # Diagnostic reports (https://nodejs.org/api/report.html) 53 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 54 | 55 | # Runtime data 56 | pids 57 | *.pid 58 | *.seed 59 | *.pid.lock 60 | 61 | # Directory for instrumented libs generated by jscoverage/JSCover 62 | lib-cov 63 | 64 | # Coverage directory used by tools like istanbul 65 | coverage 66 | *.lcov 67 | 68 | # nyc test coverage 69 | .nyc_output 70 | 71 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 72 | .grunt 73 | 74 | # Bower dependency directory (https://bower.io/) 75 | bower_components 76 | 77 | # node-waf configuration 78 | .lock-wscript 79 | 80 | # Compiled binary addons (https://nodejs.org/api/addons.html) 81 | build/Release 82 | 83 | # Dependency directories 84 | node_modules/ 85 | jspm_packages/ 86 | 87 | # Snowpack dependency directory (https://snowpack.dev/) 88 | web_modules/ 89 | 90 | # TypeScript cache 91 | *.tsbuildinfo 92 | 93 | # Optional npm cache directory 94 | .npm 95 | 96 | # Optional eslint cache 97 | .eslintcache 98 | 99 | # Microbundle cache 100 | .rpt2_cache/ 101 | .rts2_cache_cjs/ 102 | .rts2_cache_es/ 103 | .rts2_cache_umd/ 104 | 105 | # Optional REPL history 106 | .node_repl_history 107 | 108 | # Output of 'npm pack' 109 | *.tgz 110 | 111 | # Yarn Integrity file 112 | .yarn-integrity 113 | 114 | # dotenv environment variables file 115 | .env 116 | .env.test 117 | 118 | # parcel-bundler cache (https://parceljs.org/) 119 | .cache 120 | .parcel-cache 121 | 122 | # Next.js build output 123 | .next 124 | out 125 | 126 | # Nuxt.js build / generate output 127 | .nuxt 128 | dist 129 | 130 | # Gatsby files 131 | .cache/ 132 | # Comment in the public line in if your project uses Gatsby and not Next.js 133 | # https://nextjs.org/blog/next-9-1#public-directory-support 134 | # public 135 | 136 | # vuepress build output 137 | .vuepress/dist 138 | 139 | # Serverless directories 140 | .serverless/ 141 | 142 | # FuseBox cache 143 | .fusebox/ 144 | 145 | # DynamoDB Local files 146 | .dynamodb/ 147 | 148 | # TernJS port file 149 | .tern-port 150 | 151 | # Stores VSCode versions used for testing VSCode extensions 152 | .vscode-test 153 | 154 | # yarn v2 155 | .yarn/cache 156 | .yarn/unplugged 157 | .yarn/build-state.yml 158 | .yarn/install-state.gz 159 | .pnp.* 160 | 161 | # Windows thumbnail cache files 162 | Thumbs.db 163 | Thumbs.db:encryptable 164 | ehthumbs.db 165 | ehthumbs_vista.db 166 | 167 | # Dump file 168 | *.stackdump 169 | 170 | # Folder config file 171 | [Dd]esktop.ini 172 | 173 | # Recycle Bin used on file shares 174 | $RECYCLE.BIN/ 175 | 176 | # Windows Installer files 177 | *.cab 178 | *.msi 179 | *.msix 180 | *.msm 181 | *.msp 182 | 183 | # Windows shortcuts 184 | *.lnk 185 | 186 | *~ 187 | 188 | # temporary files which can be created if a process still has a handle open of a deleted file 189 | .fuse_hidden* 190 | 191 | # KDE directory preferences 192 | .directory 193 | 194 | # Linux trash folder which might appear on any partition or disk 195 | .Trash-* 196 | 197 | # .nfs files are created when an open file is removed but is still being accessed 198 | .nfs* 199 | 200 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 201 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 202 | 203 | # User-specific stuff 204 | .idea/**/workspace.xml 205 | .idea/**/tasks.xml 206 | .idea/**/usage.statistics.xml 207 | .idea/**/dictionaries 208 | .idea/**/shelf 209 | 210 | # Generated files 211 | .idea/**/contentModel.xml 212 | 213 | # Sensitive or high-churn files 214 | .idea/**/dataSources/ 215 | .idea/**/dataSources.ids 216 | .idea/**/dataSources.local.xml 217 | .idea/**/sqlDataSources.xml 218 | .idea/**/dynamic.xml 219 | .idea/**/uiDesigner.xml 220 | .idea/**/dbnavigator.xml 221 | 222 | # Gradle 223 | .idea/**/gradle.xml 224 | .idea/**/libraries 225 | 226 | # Gradle and Maven with auto-import 227 | # When using Gradle or Maven with auto-import, you should exclude module files, 228 | # since they will be recreated, and may cause churn. Uncomment if using 229 | # auto-import. 230 | # .idea/artifacts 231 | # .idea/compiler.xml 232 | # .idea/jarRepositories.xml 233 | # .idea/modules.xml 234 | # .idea/*.iml 235 | # .idea/modules 236 | # *.iml 237 | # *.ipr 238 | 239 | # CMake 240 | cmake-build-*/ 241 | 242 | # Mongo Explorer plugin 243 | .idea/**/mongoSettings.xml 244 | 245 | # File-based project format 246 | *.iws 247 | 248 | # IntelliJ 249 | out/ 250 | 251 | # mpeltonen/sbt-idea plugin 252 | .idea_modules/ 253 | 254 | # JIRA plugin 255 | atlassian-ide-plugin.xml 256 | 257 | # Cursive Clojure plugin 258 | .idea/replstate.xml 259 | 260 | # Crashlytics plugin (for Android Studio and IntelliJ) 261 | com_crashlytics_export_strings.xml 262 | crashlytics.properties 263 | crashlytics-build.properties 264 | fabric.properties 265 | 266 | # Editor-based Rest Client 267 | .idea/httpRequests 268 | 269 | # Android studio 3.1+ serialized cache file 270 | .idea/caches/build_file_checksums.ser 271 | 272 | # Cache files for Sublime Text 273 | *.tmlanguage.cache 274 | *.tmPreferences.cache 275 | *.stTheme.cache 276 | 277 | # Workspace files are user-specific 278 | *.sublime-workspace 279 | 280 | # Project files should be checked into the repository, unless a significant 281 | # proportion of contributors will probably not be using Sublime Text 282 | # *.sublime-project 283 | 284 | # SFTP configuration file 285 | sftp-config.json 286 | sftp-config-alt*.json 287 | 288 | # Package control specific files 289 | Package Control.last-run 290 | Package Control.ca-list 291 | Package Control.ca-bundle 292 | Package Control.system-ca-bundle 293 | Package Control.cache/ 294 | Package Control.ca-certs/ 295 | Package Control.merged-ca-bundle 296 | Package Control.user-ca-bundle 297 | oscrypto-ca-bundle.crt 298 | bh_unicode_properties.cache 299 | 300 | # Sublime-github package stores a github token in this file 301 | # https://packagecontrol.io/packages/sublime-github 302 | GitHub.sublime-settings 303 | 304 | # Swap 305 | [._]*.s[a-v][a-z] 306 | !*.svg # comment out if you don't need vector files 307 | [._]*.sw[a-p] 308 | [._]s[a-rt-v][a-z] 309 | [._]ss[a-gi-z] 310 | [._]sw[a-p] 311 | 312 | # Session 313 | Session.vim 314 | Sessionx.vim 315 | 316 | # Temporary 317 | .netrwhist 318 | *~ 319 | # Auto-generated tag files 320 | tags 321 | # Persistent undo 322 | [._]*.un~ 323 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "jsxSingleQuote": false, 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

vasTaiDva

2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | require.resolve("@babel/preset-env"), 4 | require.resolve("@babel/preset-typescript"), 5 | ], 6 | plugins: [require.resolve("@babel/plugin-transform-runtime")], 7 | }; 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | 3 | module.exports = { 4 | preset: 'ts-jest', 5 | displayName: { 6 | name: pkg.name, 7 | color: 'blue', 8 | }, 9 | testEnvironment: 'node', 10 | testPathIgnorePatterns: ['/lib/', '/node_modules/'], 11 | testMatch: ['/src/**/__tests__/**/*.[jt]s?(x)'], 12 | verbose: true, 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vastai-dva", 3 | "version": "1.0.17", 4 | "description": "A simple and lightweight and elm-style framework with minimal API and zero boilerplate", 5 | "repository": "https://github.com/lio-mengxiang/vastaiDva.git", 6 | "keywords": [ 7 | "dva", 8 | "mini dva", 9 | "mini redux", 10 | "sample redux" 11 | ], 12 | "license": "MIT", 13 | "author": "1334196450@qq.com", 14 | "main": "lib/js/index.js", 15 | "module": "esm/index.js", 16 | "typings": "types/index.d.ts", 17 | "files": [ 18 | "dist", 19 | "esm", 20 | "lib", 21 | "types" 22 | ], 23 | "scripts": { 24 | "clean": "rimraf types lib coverage", 25 | "test": "jest --config ./jest.config.j$s", 26 | "coverage": "rimraf coverage && jest --config ./jest.config.js --coverage", 27 | "build:types": "rimraf types && tsc --outDir types/index.d.ts -d --emitDeclarationOnly", 28 | "build:es": "rimraf esm && tsc --outDir ./esm --module ESNext --baseUrl ./src", 29 | "build:umd": "rimraf dist && mx buildLib --output-name index --mode umd", 30 | "build:cjs": "rimraf dist && mx buildLib --output-name index --mode cjs", 31 | "build": "yarn build:types && yarn build:es && yarn build:umd && yarn build:cjs", 32 | "release": "ts-node ./scripts/release.ts" 33 | }, 34 | "dependencies": { 35 | "redux": "^4.1.2" 36 | }, 37 | "devDependencies": { 38 | "@mx-design/release": "2.2.27", 39 | "@mx-design/cli": "1.0.36" 40 | }, 41 | "browserslist": [ 42 | "chrome 60", 43 | "Firefox 45", 44 | "safari 10" 45 | ], 46 | "publishConfig": { 47 | "registry": "https://registry.npmjs.org/" 48 | } 49 | } -------------------------------------------------------------------------------- /scripts/release.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getNextVersion, 3 | gitPush, 4 | build, 5 | publishNpm, 6 | updateVersion, 7 | compose, 8 | eslint, 9 | } from '@mx-design/release'; 10 | 11 | const middle = [ 12 | eslint(), 13 | getNextVersion, 14 | updateVersion, 15 | gitPush, 16 | build, 17 | publishNpm, 18 | ]; 19 | 20 | compose(middle); 21 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { IMiddleware, IExtraReducers } from './definitions'; 2 | 3 | export const SHOW = '@@LOADING/SHOW'; 4 | export const HIDE = '@@LOADING/HIDE'; 5 | export const NAMESPACE = 'loading'; 6 | export const MIDDLEWARES = 'middlewares'; 7 | export const MIDDLEWARE: IMiddleware = 'middleware'; 8 | export const EXTRA_REDUCER: IExtraReducers = 'extraReducers'; 9 | -------------------------------------------------------------------------------- /src/definitions.ts: -------------------------------------------------------------------------------- 1 | import { Reducer, Store, Middleware } from 'redux'; 2 | import { EXTRA_REDUCER, MIDDLEWARES } from './constants'; 3 | 4 | export type IAction = { type: string; payload: Record }; 5 | export type reducer = Reducer, IAction>; 6 | export type reducers = Record, IAction>>; 7 | export type IEffectFn = (...args: any[]) => Promise; 8 | export type IEffects = Record; 9 | export type store = Store; 10 | 11 | export interface IModel { 12 | namespace: string; 13 | state: Record; 14 | reducers: Record; 15 | effects?: IEffects; 16 | } 17 | export type IMiddleware = 'middleware'; 18 | export type IMiddlewares = 'middlewares'; 19 | export type IExtraReducers = 'extraReducers'; 20 | 21 | export type IHooks = { 22 | [MIDDLEWARES]?: ((...args: any[]) => Middleware)[]; 23 | [EXTRA_REDUCER]?: reducers[]; 24 | }; 25 | export type IPluginKey = IMiddlewares | IExtraReducers; 26 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | applyMiddleware, 4 | combineReducers, 5 | Store, 6 | Middleware, 7 | Dispatch, 8 | } from 'redux'; 9 | import loadingPlugin from './loadingPlugin'; 10 | import { 11 | IModel, 12 | IEffects, 13 | IAction, 14 | reducer, 15 | IExtraReducers, 16 | IMiddlewares, 17 | } from './definitions'; 18 | import { EXTRA_REDUCER, MIDDLEWARES } from './constants'; 19 | import Plugin from './plugin'; 20 | 21 | class VastaiDva { 22 | // 存放所有model 23 | _models: IModel[]; 24 | 25 | // createStore的结果 26 | store: Store; 27 | 28 | // 额外需要加载的plugin 29 | plugin: Plugin; 30 | 31 | effects: IEffects; 32 | 33 | constructor(plugin: Plugin) { 34 | this._models = []; 35 | this.effects = {}; 36 | this.plugin = plugin; 37 | } 38 | 39 | /** 40 | * 注册model的方法 41 | * @param m model 42 | */ 43 | addModel(model: IModel) { 44 | // 将model的reducer和effects加上scope 45 | const prefixmodel = this.prefixResolve(model); 46 | this._models.push(prefixmodel); 47 | } 48 | 49 | /** 50 | * 动态卸载model方法,目前没有需求暂不写 51 | */ 52 | unModel() {} 53 | 54 | /** 55 | * prefixResolve 将model的reducer和effects加上scope 56 | */ 57 | prefixResolve(model: IModel) { 58 | if (model.reducers) { 59 | model.reducers = this.addPrefix(model.reducers, model.namespace); 60 | } 61 | if (model.effects) { 62 | model.effects = this.addPrefix(model.effects, model.namespace); 63 | } 64 | this.addEffects(model.effects || {}); 65 | return model; 66 | } 67 | 68 | addEffects(effects: IEffects) { 69 | for (const [key, value] of Object.entries(effects)) { 70 | this.effects[key] = value; 71 | } 72 | } 73 | 74 | addPrefix(obj: IModel['reducers'] | IEffects, namespace: string) { 75 | return Object.keys(obj).reduce((prev, next) => { 76 | prev[`${namespace}/${next}`] = obj[next]; 77 | return prev; 78 | }, {}); 79 | } 80 | 81 | /** 82 | * 83 | * @returns 将全部reducer合并为一个reducer 84 | */ 85 | getReducer(): reducer { 86 | const reducers: IModel['reducers'] = {}; 87 | for (const m of this._models) { 88 | // m是每个model的配置 89 | reducers[m.namespace] = function ( 90 | state: Record = m.state, 91 | action: IAction 92 | ) { 93 | // 组织每个模块的reducer 94 | const everyreducers = m.reducers; // reducers的配置对象,里面是函数 95 | const reducer = everyreducers[action.type]; // 相当于以前写的switch 96 | if (reducer) { 97 | return reducer(state, action); 98 | } 99 | return state; 100 | }; 101 | } 102 | const extraReducers = this.plugin.get(EXTRA_REDUCER); 103 | return combineReducers>({ 104 | ...reducers, 105 | ...extraReducers, 106 | }); // reducer结构{reducer1:fn,reducer2:fn} 107 | } 108 | 109 | asyncMiddeWare(): Middleware { 110 | return ({ 111 | dispatch, 112 | getState, 113 | }: { 114 | dispatch: Dispatch; 115 | getState: Store['getState']; 116 | }) => { 117 | return (next) => async (action: IAction) => { 118 | if (typeof this.effects[action.type] === 'function') { 119 | return this.effects[action.type]( 120 | action.payload, 121 | dispatch, 122 | getState() 123 | ); 124 | } 125 | return next(action); 126 | }; 127 | }; 128 | } 129 | 130 | start() { 131 | const middles = this.plugin 132 | .get(MIDDLEWARES) 133 | .map((middleware) => middleware({ effects: this.effects })); 134 | const reducer = this.getReducer(); 135 | this.store = applyMiddleware( 136 | ...middles, 137 | this.asyncMiddeWare() 138 | )(createStore)(reducer); 139 | } 140 | } 141 | const plugin = new Plugin(); 142 | const VastaiDvaInstance = new VastaiDva(plugin); 143 | VastaiDvaInstance.plugin.use(loadingPlugin()); 144 | 145 | export type { IModel, IAction }; 146 | export default VastaiDvaInstance; 147 | -------------------------------------------------------------------------------- /src/loadingPlugin.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from 'redux'; 2 | import { HIDE, NAMESPACE, SHOW } from './constants'; 3 | import { reducer } from './definitions'; 4 | 5 | export default function createLoading() { 6 | const initalState: Record = { 7 | effects: {}, // 用来收集每个namespace下的effects是true还是false 8 | }; 9 | const extraReducers: { [NAMESPACE]: reducer } = { 10 | // 这里直接把写进combineReducer的reducer准备好,键名loading 11 | [NAMESPACE](state = initalState, { type, payload }) { 12 | const { actionType } = payload || {}; 13 | switch (type) { 14 | case SHOW: 15 | return { 16 | effects: { 17 | ...state.effects, 18 | [actionType]: true, 19 | }, 20 | }; 21 | case HIDE: 22 | return { 23 | effects: { 24 | ...state.effects, 25 | [actionType]: false, 26 | }, 27 | }; 28 | default: 29 | return state; 30 | } 31 | }, 32 | }; 33 | 34 | const middleware: (...args: any[]) => Middleware = 35 | ({ effects }) => 36 | ({ dispatch }) => { 37 | return (next) => async (action) => { 38 | if (typeof effects[action.type] === 'function') { 39 | dispatch({ type: SHOW, payload: { actionType: action.type } }); 40 | const result = await next(action); 41 | dispatch({ type: HIDE, payload: { actionType: action.type } }); 42 | return result; 43 | } 44 | return next(action); 45 | }; 46 | }; 47 | return { 48 | middlewares: middleware, 49 | extraReducers, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { IHooks, IPluginKey } from './definitions'; 2 | import { EXTRA_REDUCER, MIDDLEWARES } from './constants'; 3 | 4 | export default class Plugin { 5 | hooks: IHooks; 6 | 7 | constructor() { 8 | // 初始化把钩子都做成数组 9 | this.hooks = { [MIDDLEWARES]: [], [EXTRA_REDUCER]: [] }; 10 | } 11 | 12 | use(plugin: Record) { 13 | // 因为会多次use,所以就把函数或者对象push进对应的钩子里 14 | const { hooks } = this; 15 | for (const key in plugin) { 16 | if (Object.prototype.hasOwnProperty.call(plugin, key)) { 17 | hooks[key].push(plugin[key as IPluginKey]); 18 | } 19 | } 20 | } 21 | 22 | get(key: T): IHooks[T] { 23 | // 不同的钩子进行不同处理 24 | if (key === EXTRA_REDUCER) { 25 | // 处理reducer,就把所有对象并成总对象,这里只能是对象形式才能满足后面并入combine的操作。 26 | return Object.assign({}, ...this.hooks[key]); 27 | } 28 | if (key === MIDDLEWARES) { 29 | return this.hooks[key]; // 其他钩子就返回用户配置的函数或对象 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "target": "ES2019", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "importHelpers": true, 8 | "jsx": "react", 9 | "esModuleInterop": true, 10 | "resolveJsonModule": true, 11 | "sourceMap": true, 12 | "allowSyntheticDefaultImports": true, 13 | "skipLibCheck": true, 14 | "types": ["webpack", "node", "jest"] 15 | }, 16 | "include": ["src", "webpack.config.js"], 17 | "exclude": ["node_modules", "lib", "es", "dist", "**/__tests__/**", "tests"] 18 | } 19 | --------------------------------------------------------------------------------