├── packages ├── shared │ ├── index.js │ ├── .npmignore │ ├── dx-version.ts │ ├── package.json │ ├── README.md │ ├── symbol.ts │ └── package-lock.json ├── extension.typeing.ts ├── core │ ├── src │ │ ├── utils │ │ │ ├── resolve.ts │ │ │ ├── index.ts │ │ │ └── is.ts │ │ ├── helper │ │ │ ├── sec-access-map.ts │ │ │ ├── store.ts │ │ │ └── mixins.ts │ │ ├── index.ts │ │ ├── dx │ │ │ ├── create-effect │ │ │ │ ├── hooks │ │ │ │ │ ├── afterEffect.ts │ │ │ │ │ ├── beforeEffect.ts │ │ │ │ │ └── effect.ts │ │ │ │ ├── create-effect-context.ts │ │ │ │ ├── create-effect-handler.ts │ │ │ │ ├── create-effect-fork.ts │ │ │ │ ├── index.ts │ │ │ │ └── combin-saga.ts │ │ │ ├── index.ts │ │ │ ├── create-reducer │ │ │ │ ├── hooks │ │ │ │ │ ├── reducer.ts │ │ │ │ │ ├── after-reducer.ts │ │ │ │ │ └── before-reducer.ts │ │ │ │ └── index.ts │ │ │ ├── create-store │ │ │ │ ├── index.ts │ │ │ │ ├── promise-middleware.ts │ │ │ │ ├── store-model.ts │ │ │ │ └── create-store.ts │ │ │ ├── exports │ │ │ │ ├── create.ts │ │ │ │ ├── collect.ts │ │ │ │ └── models.ts │ │ │ ├── create-plugin.ts │ │ │ └── create-action.ts │ │ └── dx-model │ │ │ ├── base-effect.ts │ │ │ ├── model.ts │ │ │ └── base.ts │ ├── index.js │ ├── README.md │ ├── npm │ │ └── index.js │ └── package.json └── common │ ├── README.md │ ├── index.js │ ├── npm │ └── index.js │ ├── index.d.ts │ ├── src │ ├── index.ts │ ├── expand │ │ ├── label.ts │ │ ├── reducer.ts │ │ └── effect.ts │ └── mark.ts │ └── package.json ├── .eslintignore ├── examples ├── taro-sample │ ├── src │ │ ├── app.scss │ │ ├── pages │ │ │ └── index │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ ├── models │ │ │ └── todolist.model.js │ │ ├── index.html │ │ └── app.tsx │ ├── config │ │ ├── dev.js │ │ ├── prod.js │ │ └── index.js │ ├── .editorconfig │ ├── project.config.json │ ├── .eslintrc │ ├── global.d.ts │ ├── .npmignore │ ├── README.md │ ├── tsconfig.json │ └── package.json └── react-app-todolist │ ├── src │ ├── extension.typeing.ts │ ├── dx-plugins │ │ └── immer.ts │ ├── index.css │ ├── index.tsx │ ├── dx │ │ └── todolist.model.ts │ └── pages │ │ └── main │ │ ├── index.module.css │ │ └── index.tsx │ ├── babel.config.js │ ├── package.json │ ├── webpack.config.js │ └── tsconfig.json ├── .babelrc ├── scripts ├── jest │ ├── jest.setup.js │ └── jest.config.js ├── bundles.js ├── release.js └── build.js ├── .prettierrc ├── lerna.json ├── commitlint.config.js ├── .gitignore ├── tsconfig.dts.json ├── tsconfig.json ├── codecov.yml ├── .github └── workflows │ └── release.yml ├── .eslintrc ├── LICENSE ├── package.json ├── README.md └── CHANGELOG.md /packages/shared/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | scripts/ 2 | build/ -------------------------------------------------------------------------------- /examples/taro-sample/src/app.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/shared/.npmignore: -------------------------------------------------------------------------------- 1 | 2 | *.d.ts 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/shared/dx-version.ts: -------------------------------------------------------------------------------- 1 | export const version = '0.0.1'; 2 | -------------------------------------------------------------------------------- /scripts/jest/jest.setup.js: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/extension.typeing.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | -------------------------------------------------------------------------------- /packages/extension.typeing.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; 2 | declare const __ISSUE__: string; 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 125, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /packages/core/src/utils/resolve.ts: -------------------------------------------------------------------------------- 1 | // from rollup 2 | export function getExportFromNamespace(n: any): any { 3 | return (n && n['default']) || n; 4 | } 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const config = require('@commitlint/config-conventional'); 2 | config.rules['type-enum'][2].push('release'); 3 | module.exports = config; 4 | -------------------------------------------------------------------------------- /examples/taro-sample/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"' 4 | }, 5 | defineConstants: {}, 6 | mini: {}, 7 | h5: {} 8 | } 9 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/dx-plugins/immer.ts: -------------------------------------------------------------------------------- 1 | import produce from 'immer'; 2 | 3 | export default function(context: any): void { 4 | context.hooks('reducer', produce); 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const Dx = require('./src/index.ts'); 3 | 4 | module.exports = Dx; 5 | module.exports.default = Dx; 6 | -------------------------------------------------------------------------------- /packages/core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line prettier/prettier 2 | import * as is from './is'; 3 | import * as resolve from './resolve'; 4 | 5 | export { is, resolve }; 6 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # `@dxjs/core` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const core = require('@dxjs/core'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/common/README.md: -------------------------------------------------------------------------------- 1 | # `@dxjs/common` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const common = require('@dxjs/common'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/common/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const DxCommon = require('./src/index.ts'); 3 | 4 | module.exports = DxCommon; 5 | module.exports.default = DxCommon.default; 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .vscode 4 | dist/ 5 | build/ 6 | 7 | .cache/ 8 | 9 | # 暂时不提交 10 | # scripts/ 11 | docs/.vuepress 12 | 13 | lib/ 14 | 15 | *-lock.json 16 | *.lock 17 | 18 | coverage/ 19 | 20 | .temp -------------------------------------------------------------------------------- /packages/core/src/helper/sec-access-map.ts: -------------------------------------------------------------------------------- 1 | export function securityAccessMap(map: Map, key: T, defaultValue: R): R { 2 | const value = map.get(key) ?? defaultValue; 3 | map.set(key, value); 4 | return value; 5 | } 6 | -------------------------------------------------------------------------------- /packages/core/npm/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./cjs/core.production.min.js'); 5 | } else { 6 | module.exports = require('./cjs/core.development.js'); 7 | } 8 | -------------------------------------------------------------------------------- /packages/common/npm/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./cjs/common.production.min.js'); 5 | } else { 6 | module.exports = require('./cjs/common.development.js'); 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'es6-symbol'; 3 | 4 | export { DxModel } from './dx-model/model'; 5 | export { Dx } from './dx'; 6 | export { connect, useDispatch, useSelector, useStore, shallowEqual, batch, Provider } from 'react-redux'; 7 | -------------------------------------------------------------------------------- /examples/taro-sample/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /tsconfig.dts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "allowSyntheticDefaultImports": true, 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "declarationDir": "build" 9 | }, 10 | "exclude": ["node_modules", "scripts"] 11 | } 12 | -------------------------------------------------------------------------------- /examples/taro-sample/src/pages/index/index.css: -------------------------------------------------------------------------------- 1 | 2 | .index { 3 | padding: 40px; 4 | } 5 | 6 | .input { 7 | border-bottom: 1px solid #efefef; 8 | margin-bottom: 40px; 9 | padding: 10px 20px; 10 | } 11 | 12 | .item { 13 | margin-bottom: 10px; 14 | padding: 10px 20px; 15 | background: #eee; 16 | display: block; 17 | font-size: 36px; 18 | } 19 | -------------------------------------------------------------------------------- /examples/taro-sample/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./dist", 3 | "projectname": "taro-sample1", 4 | "description": "dxjs example for taro", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": false, 9 | "postcss": false, 10 | "minified": false 11 | }, 12 | "compileType": "miniprogram" 13 | } 14 | -------------------------------------------------------------------------------- /packages/common/index.d.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'es6-symbol'; 3 | export { Label } from './src/expand/label'; 4 | export { Effect } from './src/expand/effect'; 5 | export { Reducer } from './src/expand/reducer'; 6 | export declare const TakeEvery: string | symbol; 7 | export declare const TakeLeading: string | symbol; 8 | export declare const TakeLatest: string | symbol; 9 | export declare const Throttle: string | symbol; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@chores/tsconfig", 3 | "compilerOptions": { 4 | "lib": ["DOM", "ESNext", "ESNext.Symbol"], 5 | "noImplicitAny": true, 6 | "emitDecoratorMetadata": true, 7 | "downlevelIteration": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "module": "ESNext" 11 | }, 12 | "include": ["packages/**/*"], 13 | "exclude": ["node_modules", "scripts"] 14 | } 15 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/taro-sample/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"' 4 | }, 5 | defineConstants: {}, 6 | mini: {}, 7 | h5: { 8 | /** 9 | * 如果h5端编译后体积过大,可以使用webpack-bundle-analyzer插件对打包体积进行分析。 10 | * 参考代码如下: 11 | * webpackChain (chain) { 12 | * chain.plugin('analyzer') 13 | * .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 14 | * } 15 | */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/taro-sample/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["taro"], 3 | "rules": { 4 | "no-unused-vars": ["error", { "varsIgnorePattern": "Taro" }], 5 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".tsx"] }] 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "useJSXTextNode": true, 13 | "project": "./tsconfig.json" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-app-todolist/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], 3 | plugins: [ 4 | '@babel/plugin-transform-runtime', 5 | [ 6 | '@babel/plugin-proposal-decorators', 7 | { 8 | legacy: true, 9 | }, 10 | ], 11 | [ 12 | '@babel/plugin-proposal-class-properties', 13 | { 14 | loose: true, 15 | }, 16 | ], 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /packages/common/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Label } from './expand/label'; 2 | export { Effect } from './expand/effect'; 3 | export { Reducer } from './expand/reducer'; 4 | export { mark as Mark } from './mark'; 5 | 6 | import { TAKE_EVERY, TAKE_LEADING, TAKE_LATEST, THROTTLE } from '@dxjs/shared/symbol'; 7 | 8 | export const TakeEvery = TAKE_EVERY; 9 | export const TakeLeading = TAKE_LEADING; 10 | export const TakeLatest = TAKE_LATEST; 11 | export const Throttle = THROTTLE; 12 | -------------------------------------------------------------------------------- /examples/taro-sample/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png"; 2 | declare module "*.gif"; 3 | declare module "*.jpg"; 4 | declare module "*.jpeg"; 5 | declare module "*.svg"; 6 | declare module "*.css"; 7 | declare module "*.less"; 8 | declare module "*.scss"; 9 | declare module "*.sass"; 10 | declare module "*.styl"; 11 | 12 | // @ts-ignore 13 | declare const process: { 14 | env: { 15 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq'; 16 | [key: string]: any; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/taro-sample/.npmignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | dist/ 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | # temp 23 | .temp/ 24 | .rn_temp/ 25 | deploy_versions/ 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | -------------------------------------------------------------------------------- /packages/core/src/utils/is.ts: -------------------------------------------------------------------------------- 1 | interface IsGeneratorExtend extends Function { 2 | isGenerator(): boolean; 3 | } 4 | 5 | export function isClass(fn: unknown): fn is T { 6 | return typeof fn === 'function' && !!fn.toString().match(/^class/); 7 | } 8 | 9 | export function isGenerator(fn?: IsGeneratorExtend | Function): fn is GeneratorFunction { 10 | if (!fn) return false; 11 | if ('isGenerator' in fn) return fn.isGenerator(); 12 | return fn.constructor && 'GeneratorFunction' == fn.constructor.name; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/hooks/afterEffect.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 17:02:11 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-10-14 14:35:03 6 | */ 7 | 8 | import { store } from '../../../helper/store'; 9 | 10 | function afterEffectHook(context: T): void { 11 | const afterEffects = (store.plugins.get('afterEffect') as ((context: T) => void)[]) ?? []; 12 | 13 | afterEffects.forEach(hook => { 14 | hook(context); 15 | }); 16 | } 17 | 18 | export default afterEffectHook; 19 | -------------------------------------------------------------------------------- /packages/core/src/dx/index.ts: -------------------------------------------------------------------------------- 1 | import { createFactory } from './exports/create'; 2 | import { modelsFactory } from './exports/models'; 3 | import { collectFactory } from './exports/collect'; 4 | import { createStoreFactory } from './create-store'; 5 | 6 | export function DxFactory() { 7 | return { 8 | createStore: createStoreFactory(), 9 | create: createFactory(), 10 | // 后面可能会有所改动 11 | UNSAFE_getModels: modelsFactory(), 12 | UNSAFE_collect: collectFactory(), 13 | }; 14 | } 15 | 16 | export const Dx = DxFactory(); 17 | -------------------------------------------------------------------------------- /examples/taro-sample/src/models/todolist.model.js: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro' 2 | import { Dx, DxModel } from '@dxjs/core' 3 | import { Reducer, Effect } from '@dxjs/common' 4 | 5 | 6 | @Dx.collect() 7 | class TodolistModel extends DxModel { 8 | 9 | state = { 10 | data: [] 11 | } 12 | 13 | @Reducer() 14 | addToData(text) { 15 | return { data: [...this.state.data, text] } 16 | } 17 | 18 | // TODO: taro 不支持装饰器与生成函数同时使用 19 | *asyncAddToData(text) { 20 | yield this.$delay(1000) 21 | yield this.$put(TodolistModel.addToData(text)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | 7 | env: 8 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Install Dep 15 | uses: borales/actions-yarn@v2.0.0 16 | with: 17 | cmd: install 18 | 19 | - name: Run Test 20 | uses: borales/actions-yarn@v2.0.0 21 | with: 22 | cmd: test:cov 23 | 24 | - name: Upload coverage 25 | uses: codecov/codecov-action@v1.0.2 26 | with: 27 | token: ${{secrets.CODECOV_TOKEN}} 28 | 29 | -------------------------------------------------------------------------------- /examples/taro-sample/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 在 Taro 中使用 Dxjs 3 | 4 | 由于 Taro 环境与普通的 React 构建环境有些许差异,所以需要做一些简单的适配工作 5 | 6 | 1. config 中设置 alias 7 | ```javascript 8 | // config/index.js 9 | alias: { 10 | "react": require.resolve("@tarojs/taro"), 11 | "react-dom": require.resolve("@tarojs/taro"), 12 | } 13 | ``` 14 | 15 | 2. 由于 taro 默认的 babel 环境不兼容装饰器与生成函数同时使用,所以在 taro 环境中,model 中所有的生成器函数都被认为是 effect,坏处就是无法使用像 Label 等装饰器,如果有兼容方案,望告知 16 | ```javascript 17 | // config/index.js 18 | class Model extends DxModel { 19 | 20 | // 不支持 21 | @Effect() 22 | *asyncUpdate() { 23 | 24 | } 25 | 26 | // 支持 27 | *asyncUpdate() { 28 | 29 | } 30 | } 31 | ``` 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import React from 'react'; 4 | import { render, version } from 'react-dom'; 5 | import { Dx } from '@dxjs/core'; 6 | import Main from './pages/main'; 7 | import './dx/todolist.model'; 8 | import immer from './dx-plugins/immer'; 9 | 10 | const root = document.createElement('div'); 11 | document.body.appendChild(root); 12 | 13 | const DxApp = Dx.create({ 14 | plugins: [immer], 15 | }); 16 | 17 | render( 18 | 19 | 20 |
21 | 22 | , 23 | root, 24 | ); 25 | -------------------------------------------------------------------------------- /packages/core/src/dx-model/base-effect.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { AnyAction } from 'redux'; 3 | 4 | export class BaseEffect { 5 | constructor(private context: any) {} 6 | 7 | getState(): any { 8 | return this.context.getState(); 9 | } 10 | 11 | getPayload(): T['payload'] { 12 | return this.context.action.payload; 13 | } 14 | 15 | dispatchCurrentAction

(payload?: P): T { 16 | const action = { 17 | ...this.context.action, 18 | payload: typeof payload === 'undefined' ? this.context.action.payload : payload, 19 | }; 20 | this.context.dispatch(action); 21 | return action; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-reducer/hooks/reducer.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../../../helper/store'; 2 | import { Reducer } from 'redux'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | export type ReducerEnhancer = (reducer: Reducer) => T; 6 | 7 | type reducerFn = (f: Reducer) => Reducer; 8 | 9 | export function reducerHook(): ReducerEnhancer { 10 | // 全局的,本地的 11 | const reducerHooks = store.plugins.get('reducer') as reducerFn[]; 12 | if (!reducerHooks || !reducerHooks.length) return (f: Reducer): Reducer => f; 13 | return reducerHooks.reduce((a: ReducerEnhancer, b: ReducerEnhancer) => { 14 | return (reducer: Reducer): Reducer => a(b(reducer)); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/common/src/expand/label.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 19:20:35 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-08-10 19:30:45 6 | */ 7 | 8 | /* eslint-disable @typescript-eslint/no-explicit-any */ 9 | import { LABEL } from '@dxjs/shared/symbol'; 10 | import { mark } from '../mark'; 11 | 12 | export function Label(...labels: string[]): MethodDecorator & ClassDecorator { 13 | return ( 14 | target: any, 15 | key?: string | symbol, 16 | descriptor?: TypedPropertyDescriptor, 17 | ): TypedPropertyDescriptor | any => { 18 | labels.forEach(label => mark(LABEL, label)(target, key!, descriptor!)); 19 | return descriptor || target; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/common/src/expand/reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 19:27:00 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-08-11 16:21:32 6 | */ 7 | /* eslint-disable @typescript-eslint/no-explicit-any */ 8 | import { REDUCER_METHODS_KEY } from '@dxjs/shared/symbol'; 9 | import { mark } from '../mark'; 10 | 11 | export function Reducer(type?: string | symbol): MethodDecorator { 12 | return (target: any, key: string | symbol, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 13 | // 通过 action type 获取对应的 target 方法,所以用 Map 结构 14 | 15 | mark(REDUCER_METHODS_KEY, [type || Symbol((key as string) ?? '__action'), key])(target.constructor); 16 | return descriptor ?? target; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-store/index.ts: -------------------------------------------------------------------------------- 1 | import { Action, Store } from 'redux'; 2 | import { store } from '../../helper/store'; 3 | import { storeModel } from './store-model'; 4 | import { combinStore } from './create-store'; 5 | import storePlugins from '../create-plugin'; 6 | import { CreateOption } from '../exports/create'; 7 | 8 | export function createStoreFactory() { 9 | return (options: CreateOption = {}): Store<{}, Action> => { 10 | // TODO: HMR 11 | if (store.reduxStore) return store.reduxStore; 12 | 13 | // 收集 plugins 14 | storePlugins(options.plugins); 15 | 16 | // 收集 model 17 | storeModel(options); 18 | 19 | // 生成 store 20 | const newStore = combinStore(options); 21 | store.reduxStore = newStore; 22 | return newStore; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | env: 14 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Setup Node 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: "12.x" 24 | registry-url: "https://registry.npmjs.org" 25 | 26 | - name: Install Dep 27 | run: yarn 28 | 29 | - name: Build 30 | run: yarn build 31 | # publish to npm 32 | 33 | - name: Publish 34 | run: npm publish build/common 35 | run: npm publish build/core 36 | run: npm publish build/shared 37 | -------------------------------------------------------------------------------- /packages/core/src/helper/store.ts: -------------------------------------------------------------------------------- 1 | import { Store } from 'redux'; 2 | import { SymbolType } from '@dxjs/shared/symbol'; 3 | import { DxModelContstructor } from '../dx-model/model'; 4 | import { Hook } from '../dx/create-plugin'; 5 | 6 | interface ModelRefs { 7 | set: Set; 8 | map: Record; 9 | } 10 | 11 | export const store = { 12 | // plugin 13 | plugins: new Map(), 14 | 15 | // models 16 | models: { 17 | set: new Set(), 18 | map: {}, 19 | } as ModelRefs, 20 | 21 | reduxStore: null as Store | null, 22 | 23 | effectTypes: new Set(), 24 | 25 | getModels(): ModelRefs { 26 | if (!store.models) { 27 | store.models = { set: new Set(), map: {} }; 28 | } 29 | return store.models; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-reducer/hooks/after-reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 11:35:26 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-08-10 11:45:34 6 | * 7 | * after reducer 允许访问 state 以及 action 8 | */ 9 | 10 | import { store } from '../../../helper/store'; 11 | import { AnyAction } from 'redux'; 12 | import { DxModel } from '../../../dx-model/model'; 13 | 14 | type afterReducerHookType = (context: T) => T; 15 | 16 | export function afterReducerHook(state: T, action: AnyAction, model: DxModel): void { 17 | const afterReducerHooks = store.plugins.get('afterReducer') as afterReducerHookType[]; 18 | if (!afterReducerHooks || !afterReducerHooks.length) return; 19 | const context = { state, action, model }; 20 | afterReducerHooks.forEach(hook => hook(context)); 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-reducer/hooks/before-reducer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 11:40:55 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-08-10 11:45:08 6 | * 7 | * before hooks 能够修改 action,以及获取 state 8 | */ 9 | 10 | import { store } from '../../../helper/store'; 11 | import { AnyAction } from 'redux'; 12 | import { DxModel } from '../../../dx-model/model'; 13 | 14 | type beforeReducerHookType = (context: T) => T; 15 | 16 | export function beforeReducerHook(state: T, action: AnyAction, model: DxModel): void { 17 | const beforeReducerHooks = store.plugins.get('beforeReducer') as beforeReducerHookType[]; 18 | if (!beforeReducerHooks || !beforeReducerHooks.length) return; 19 | const context = { state, action, model }; 20 | beforeReducerHooks.forEach(hook => hook(context)); 21 | } 22 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxjs/shared", 3 | "version": "2.0.5", 4 | "description": "Unified management of your application status, use enhancer to deal with other tedious things", 5 | "keywords": [ 6 | "dxjs", 7 | "react-redux", 8 | "react-saga", 9 | "react-dxjs", 10 | "react", 11 | "saga", 12 | "redux", 13 | "model" 14 | ], 15 | "author": "mro <254225961@qq.com>", 16 | "homepage": "https://github.com/taixw2/dx#readme", 17 | "license": "MIT", 18 | "main": "index.js", 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org/" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/taixw2/dx.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/taixw2/dx/issues" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/dx/todolist.model.ts: -------------------------------------------------------------------------------- 1 | import { Dx, DxModel, Dispatch } from '@dxjs/core'; 2 | import { Reducer, Effect } from '@dxjs/common'; 3 | 4 | @Dx.collect() 5 | export class TodolistModel extends DxModel { 6 | static addToData: Dispatch; 7 | 8 | state = { 9 | data: [] as string[], 10 | }; 11 | 12 | @Reducer() 13 | addToData(text: string): void { 14 | this.state.data.push(text + Math.random().toFixed(6)); 15 | // if no use immer 16 | // return { ...this.state, data: [...this.state.data] }; 17 | } 18 | 19 | @Reducer() 20 | removeToData(index: number): void { 21 | this.state.data.splice(index, 1); 22 | } 23 | 24 | @Effect() 25 | *asyncAddToData(text: string): Generator { 26 | const action = TodolistModel.addToData(text, false); 27 | yield this.$delay(1000); 28 | yield this.$put(action); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/taro-sample/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "jsx": "preserve", 19 | "jsxFactory": "Taro.createElement", 20 | "allowJs": true, 21 | "resolveJsonModule": true, 22 | "typeRoots": [ 23 | "node_modules/@types", 24 | "global.d.ts" 25 | ] 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "dist" 30 | ], 31 | "compileOnSave": false 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/create-effect-context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { store } from '../../helper/store'; 3 | import { AnyAction } from 'redux'; 4 | import { DxModel } from '../../dx-model/model'; 5 | import { EffectTypeInterface } from './index'; 6 | 7 | export interface BaseEffectContextInterface { 8 | action: T; 9 | 10 | dispatch: (action: AnyAction) => AnyAction | void; 11 | 12 | meta: EffectTypeInterface; 13 | 14 | getState: () => any; 15 | 16 | [key: string]: any; 17 | } 18 | 19 | export function createEffectContext(model: DxModel, meta: EffectTypeInterface, action: T): BaseEffectContextInterface { 20 | return { 21 | meta, 22 | action, 23 | model, 24 | getState: (): unknown => store?.reduxStore?.getState(), 25 | dispatch: (action: AnyAction) => store?.reduxStore?.dispatch(action), 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/hooks/beforeEffect.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 15:20:45 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-08-12 16:19:55 6 | */ 7 | 8 | import { store } from '../../../helper/store'; 9 | 10 | const beforeEffectHook = (context: T): Promise => { 11 | const beforeEffects = store.plugins.get('beforeEffect') as ((ctx: T, next: () => void) => unknown)[]; 12 | if (!beforeEffects?.length) { 13 | return Promise.resolve(); 14 | } 15 | 16 | function dispatch(i: number): Promise { 17 | const fn = beforeEffects?.[i]; 18 | if (!fn) return Promise.resolve(); 19 | 20 | try { 21 | return Promise.resolve(fn(context, () => dispatch(i + 1))); 22 | } catch (error) { 23 | return Promise.reject(error); 24 | } 25 | } 26 | 27 | return dispatch(0); 28 | }; 29 | 30 | export default beforeEffectHook; 31 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "plugin:@typescript-eslint/recommended", 5 | "prettier/@typescript-eslint", 6 | "plugin:prettier/recommended", 7 | "prettier/react" 8 | ], 9 | "settings": { 10 | "react": { 11 | "version": "detect" 12 | } 13 | }, 14 | "plugins": ["@typescript-eslint", "prettier"], 15 | "env": { 16 | "browser": true, 17 | "node": true, 18 | "es6": true, 19 | "mocha": true 20 | }, 21 | "rules": { 22 | "prettier/prettier": 1, 23 | "prefer-const": ["error"], 24 | "@typescript-eslint/indent": ["error", 2], 25 | "@typescript-eslint/no-unused-vars": 2, 26 | "@typescript-eslint/no-var-requires": 0, 27 | "@typescript-eslint/no-triple-slash-reference": 0, 28 | "@typescript-eslint/no-non-null-assertion": 0, 29 | "@typescript-eslint/explicit-function-return-type": 0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/shared/README.md: -------------------------------------------------------------------------------- 1 | # dxjs 2 | 3 | 基于 Redux、Redux-saga、Typescripe 的库,用来组织应用中的副作用和管理状态 4 | 5 | ### 快速开始 6 | 7 | ```javascript 8 | // 入口 9 | import React from "react" 10 | import { Dx } from "@dxjs/core" 11 | import App from "./app" 12 | 13 | const DxApp = Dx.create() 14 | 15 | ReactDOM.render((), document.getElementById('root')) 16 | 17 | // model 18 | import { Dx, DxMoel } from "@dxjs/core" 19 | import { Reducer, Effect } from "@dxjs/common" 20 | 21 | @Dx.collect() 22 | class Counter extends DxMoel { 23 | state = { 24 | count: 0 25 | } 26 | 27 | @Reducer 28 | updateState(count, state) { 29 | this.state.count = count 30 | } 31 | 32 | @Effect 33 | *asyncUpdateState() { 34 | yield this.$delay(1000) 35 | yield this.$put(Counter.updateState(200)) 36 | } 37 | } 38 | 39 | ### example 40 | 41 | - [sample \(by create-react-app\)](./examples/create-react-app) 42 | 43 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-store/promise-middleware.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/camelcase */ 2 | import { store } from '../../helper/store'; 3 | import { MiddlewareAPI, AnyAction, Dispatch } from 'redux'; 4 | 5 | type MiddlewareyCurry = (next: Dispatch) => (action: AnyAction) => AnyAction | Promise; 6 | export function promiseMiddleware() { 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | return (_: MiddlewareAPI): MiddlewareyCurry => { 9 | return next => (action): AnyAction | Promise => { 10 | if (store.effectTypes && store.effectTypes.has(action.type)) { 11 | return new Promise((resolve, reject) => { 12 | next({ 13 | ...action, 14 | __dxjs_resolve: resolve, 15 | __dxjs_reject: reject, 16 | }); 17 | }); 18 | } 19 | return next(action); 20 | }; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/common/src/mark.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { SymbolType } from '@dxjs/shared/symbol'; 3 | 4 | export function mark(labelKey: SymbolType, labelValue: T): MethodDecorator & ClassDecorator { 5 | return ( 6 | target: any, 7 | key?: string | symbol, 8 | descriptor?: TypedPropertyDescriptor, 9 | ): TypedPropertyDescriptor | any => { 10 | if (descriptor && key) { 11 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 12 | const previousLabels = Reflect.getMetadata(labelKey, target.constructor, key) || []; 13 | Reflect.defineMetadata(labelKey, [...new Set([...previousLabels, labelValue])], target.constructor, key); 14 | return descriptor; 15 | } 16 | 17 | const previousLabels = Reflect.getMetadata(labelKey, target) || []; 18 | Reflect.defineMetadata(labelKey, [...new Set([...previousLabels, labelValue])], target); 19 | return target; 20 | }; 21 | } 22 | 23 | // 标记 24 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/create-effect-handler.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from 'redux'; 2 | import { is } from '../../utils'; 3 | import { DxModel } from '../../dx-model/model'; 4 | import { EffectTypeInterface } from './index'; 5 | 6 | export function createEffectHandler(model: DxModel, meta: EffectTypeInterface) { 7 | return function*(action: AnyAction): Generator { 8 | try { 9 | const currentEffect = Reflect.get(model, meta.name); 10 | if (__DEV__) { 11 | require('invariant')( 12 | currentEffect && is.isGenerator(currentEffect), 13 | '副作用函数不是一个 generator 函数,函数名为: %s, 如果确定不是此原因,请提交 issus: %s', 14 | meta.name, 15 | '__ISSUE__', 16 | ); 17 | } 18 | 19 | const resolve = yield currentEffect.call(model, action.payload, action); 20 | action.__dxjs_resolve?.(resolve); 21 | return resolve; 22 | } catch (error) { 23 | action.__dxjs_reject?.(error); 24 | throw error; 25 | } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/dx/exports/create.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { createStoreFactory } from '../create-store'; 4 | import { Middleware } from 'redux'; 5 | import { EffectMiddleware } from 'redux-saga'; 6 | import { DxModelContstructor } from '../../dx-model/model'; 7 | import { DxPlugin } from '../create-plugin'; 8 | 9 | export interface CreateOption { 10 | models?: DxModelContstructor[] | { [key: string]: DxModelContstructor }; 11 | middlewares?: Middleware[]; 12 | sagaMiddlewares?: EffectMiddleware[]; 13 | plugins?: DxPlugin[]; 14 | onSagaError?: ( 15 | error: Error, 16 | errorInfo: { 17 | sagaStack: string; 18 | }, 19 | ) => void; 20 | } 21 | 22 | export function createFactory() { 23 | return (options?: CreateOption): React.FunctionComponent => { 24 | const store = createStoreFactory()(options); 25 | return ({ children }: React.PropsWithChildren<{}>) => React.createElement(Provider, { store }, children); 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/helper/mixins.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | // eslint-disable-next-line @typescript-eslint/explicit-function-return-type 4 | 5 | import { SymbolType } from '@dxjs/shared/symbol'; 6 | 7 | export function mix(...mixins: any[]): any { 8 | function copyProps(target: any, source: any): void { 9 | ([] as SymbolType[]) 10 | .concat(Object.getOwnPropertyNames(source)) 11 | .concat(Object.getOwnPropertySymbols(source)) 12 | .forEach((prop: SymbolType) => { 13 | Reflect.set(target, prop, source[prop]); 14 | }); 15 | } 16 | 17 | class MixClass { 18 | constructor(...args: any[]) { 19 | mixins.forEach(Mixin => { 20 | copyProps(this, new Mixin(...args)); 21 | }); 22 | } 23 | } 24 | 25 | for (const Mixin of mixins) { 26 | copyProps(MixClass, Mixin); 27 | copyProps(MixClass.prototype, Mixin.prototype); 28 | } 29 | return MixClass; 30 | } 31 | -------------------------------------------------------------------------------- /examples/taro-sample/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |

18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/core/src/dx/exports/collect.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { MODEL_NAME } from '@dxjs/shared/symbol'; 3 | import { store } from '../../helper/store'; 4 | import { DxModel, DxModelContstructor } from '../../dx-model/model'; 5 | 6 | // TODO: support HMR 7 | export function collectFactory() { 8 | return (name?: string) => { 9 | return function Decorate(ModelTarget: DxModelContstructor): DxModelContstructor { 10 | if (__DEV__) { 11 | require('invariant')( 12 | ModelTarget.prototype instanceof DxModel, 13 | 'collect model 必须继承自 DxModel, 当前 model 类型为 %s', 14 | typeof ModelTarget.prototype, 15 | ); 16 | 17 | require('invariant')(!store.reduxStore, 'store 已经存在, 不能再 store 创建之后增加 model'); 18 | } 19 | 20 | const models = store.getModels(); 21 | const _name = name || ModelTarget.name; 22 | Reflect.defineMetadata(MODEL_NAME, _name, ModelTarget); 23 | models.map[_name] = ModelTarget; 24 | models.set.add(ModelTarget); 25 | return ModelTarget; 26 | }; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/create-effect-fork.ts: -------------------------------------------------------------------------------- 1 | import { TAKE_LATEST, TAKE_EVERY, TAKE_LEADING, THROTTLE } from '@dxjs/shared/symbol'; 2 | import { spawn, takeLatest, takeEvery, takeLeading, throttle, ForkEffect } from 'redux-saga/effects'; 3 | import { AnyAction } from 'redux'; 4 | import { EffectTypeInterface } from './index'; 5 | 6 | export function createEffectFork(meta: EffectTypeInterface, effectHandler: (action: AnyAction) => Generator): ForkEffect { 7 | return spawn(function*() { 8 | switch (meta.helperType) { 9 | case TAKE_EVERY: 10 | yield takeEvery(meta.actionType, effectHandler); 11 | break; 12 | case TAKE_LATEST: 13 | yield takeLatest(meta.actionType, effectHandler); 14 | break; 15 | case TAKE_LEADING: 16 | yield takeLeading(meta.actionType, effectHandler); 17 | break; 18 | case THROTTLE: 19 | yield throttle(meta.value[0] ?? 350, meta.actionType, effectHandler); 20 | break; 21 | default: 22 | yield takeEvery(meta.actionType, effectHandler); 23 | break; 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /scripts/bundles.js: -------------------------------------------------------------------------------- 1 | exports.packages = [ 2 | { 3 | entry: '@dxjs/core', 4 | global: 'Dx', 5 | externals: [ 6 | { entry: 'react' }, 7 | { entry: 'react-dom' }, 8 | { entry: 'redux' }, 9 | { entry: 'react-redux' }, 10 | { entry: 'redux-saga' }, 11 | { entry: 'redux-saga/effects' }, 12 | { entry: '@dxjs/common' }, 13 | { entry: 'reflect-metadata' }, 14 | { entry: 'es6-symbol' }, 15 | ], 16 | }, 17 | { 18 | entry: '@dxjs/common', 19 | global: 'DxCommon', 20 | externals: [ 21 | { entry: 'react' }, 22 | { entry: 'react-dom' }, 23 | { entry: 'redux-saga' }, 24 | { entry: 'redux-saga/effects' }, 25 | { entry: 'react-redux' }, 26 | { entry: 'reflect-metadata' }, 27 | { entry: 'es6-symbol' }, 28 | ], 29 | }, 30 | ]; 31 | 32 | const UMD = 'UMD'; 33 | const UMD_DEV = 'UMD_DEV'; 34 | const CJS = 'CJS'; 35 | const CJS_DEV = 'CJS_DEV'; 36 | 37 | exports.bundleTypes = [UMD, UMD_DEV, CJS, CJS_DEV]; 38 | exports.UMD = UMD; 39 | exports.UMD_DEV = UMD_DEV; 40 | exports.CJS = CJS; 41 | exports.CJS_DEV = CJS_DEV; 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ouyangxin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: 欧阳鑫 3 | * @Date: 2020-08-10 08:52:15 4 | * @Last Modified by: 欧阳鑫 5 | * @Last Modified time: 2020-10-14 16:59:38 6 | */ 7 | 8 | import { store } from '../helper/store'; 9 | import { is } from '../utils'; 10 | 11 | export type Hook = 12 | | 'beforeDispatch' 13 | | 'afterDispatch' 14 | | 'beforeEffect' 15 | | 'effect' 16 | | 'afterEffect' 17 | | 'beforeReducer' 18 | | 'reducer' 19 | | 'afterReducer'; 20 | 21 | export const PluginContext = { 22 | hooks(hook: Hook, handler: unknown) { 23 | const hooks = store.plugins.get(hook) ?? []; 24 | hooks.push(handler); 25 | store.plugins.set(hook, hooks); 26 | }, 27 | }; 28 | 29 | export type ClassPlugin = { 30 | apply(ctx: typeof PluginContext): void; 31 | }; 32 | 33 | export type DxPlugin = { new (): ClassPlugin } | ((ctx: typeof PluginContext) => void); 34 | 35 | // store plugin 36 | export default function createPlugin(plugins?: DxPlugin[]) { 37 | if (!Array.isArray(plugins)) return; 38 | plugins.forEach(plugin => { 39 | if (is.isClass<{ new (): ClassPlugin }>(plugin)) { 40 | new plugin().apply(PluginContext); 41 | return; 42 | } 43 | plugin(PluginContext); 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/dx-model/model.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import { useSelector } from 'react-redux'; 4 | import { Reducer } from '@dxjs/common'; 5 | import { DxActionCreate } from '../dx/create-action'; 6 | import { DxBase } from './base'; 7 | 8 | export interface DxModelContstructor { 9 | namespace: string; 10 | 11 | new (): DxModel; 12 | } 13 | 14 | export class DxModel extends DxBase { 15 | static namespace: string = Math.random().toString(); // 用户没有设置,就使用随机的 namespace 16 | 17 | static patch: DxActionCreate; 18 | 19 | static selector() { 20 | return (state: any) => state[this.namespace] as T; 21 | } 22 | 23 | static useSelector() { 24 | return useSelector(this.selector()); 25 | } 26 | 27 | static action(actionType: string, payload: any) { 28 | (this as any)[actionType]?.(payload); 29 | } 30 | 31 | init?(): void; 32 | state = {} as S; 33 | 34 | // 内置的 reducer 35 | @Reducer() 36 | patch(payload: S) { 37 | Object.assign(this.state, payload); 38 | return this.state; 39 | } 40 | } 41 | 42 | export function isDxModel(model: any): model is DxModelContstructor { 43 | return model && model.prototype instanceof DxModel; 44 | } 45 | -------------------------------------------------------------------------------- /examples/taro-sample/src/app.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro' 2 | import Index from './pages/index' 3 | import { Provider } from '@tarojs/redux' 4 | import { Dx } from '@dxjs/core' 5 | import './app.scss' 6 | 7 | // 引入所有的 models 8 | import "./models/todolist.model"; 9 | 10 | const store = Dx.createStore() 11 | 12 | class App extends Component { 13 | 14 | componentDidMount () {} 15 | 16 | componentDidShow () {} 17 | 18 | componentDidHide () {} 19 | 20 | componentDidCatchError () {} 21 | 22 | /** 23 | * 指定config的类型声明为: Taro.Config 24 | * 25 | * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型 26 | * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string 27 | * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型 28 | */ 29 | config: Config = { 30 | pages: [ 31 | 'pages/index/index' 32 | ], 33 | window: { 34 | backgroundTextStyle: 'light', 35 | navigationBarBackgroundColor: '#fff', 36 | navigationBarTitleText: 'WeChat', 37 | navigationBarTextStyle: 'black' 38 | } 39 | } 40 | 41 | // 在 App 类中的 render() 函数没有实际作用 42 | // 请勿修改此函数 43 | render () { 44 | return ( 45 | 46 | 47 | 48 | ) 49 | } 50 | } 51 | 52 | Taro.render(, document.getElementById('app')) 53 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/index.ts: -------------------------------------------------------------------------------- 1 | import { EFFECT_METHODS_META, SymbolType } from '@dxjs/shared/symbol'; 2 | import { all, AllEffect, ForkEffect } from 'redux-saga/effects'; 3 | import { store } from '../../helper/store'; 4 | import { combinSaga } from './combin-saga'; 5 | import { DxModel } from '../../dx-model/model'; 6 | 7 | export interface EffectTypeInterface { 8 | name: SymbolType; 9 | // action type 10 | actionType: SymbolType; 11 | /** 12 | * saga helper 13 | * TAKE_EVERY,TAKE_LATEST,TAKE_LEADING,THROTTLE 14 | */ 15 | helperType: SymbolType; 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | value?: any; 18 | } 19 | 20 | export function createEffect(model: DxModel): void | (() => Generator>) { 21 | const Model = model.constructor; 22 | const effects: Set = Reflect.getMetadata(EFFECT_METHODS_META, Model); 23 | if (!effects) return; 24 | 25 | // hackTaro(model); 26 | 27 | const actionTypes = store.effectTypes ?? (store.effectTypes = new Set()); 28 | const currentSaga: ForkEffect[] = Array.from(effects).map(item => { 29 | actionTypes.add(item.actionType); 30 | return combinSaga(model, item); 31 | }); 32 | 33 | return function* saga(): Generator> { 34 | yield all(currentSaga); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxjs/common", 3 | "version": "2.0.5", 4 | "description": "Unified management of your application status, use enhancer to deal with other tedious things", 5 | "keywords": [ 6 | "dxjs", 7 | "react-redux", 8 | "react-saga", 9 | "react-dxjs", 10 | "react", 11 | "saga", 12 | "redux", 13 | "model" 14 | ], 15 | "author": "mro <254225961@qq.com>", 16 | "homepage": "https://github.com/taixw2/dx#readme", 17 | "license": "MIT", 18 | "main": "index.js", 19 | "types": "./src/index.ts", 20 | "publishConfig": { 21 | "access": "public", 22 | "registry": "https://registry.npmjs.org/" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/taixw2/dx.git" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/taixw2/dx/issues" 30 | }, 31 | "files": [ 32 | "LICENSE", 33 | "README.md", 34 | "index.js", 35 | "cjs", 36 | "umd", 37 | "src" 38 | ], 39 | "devDependencies": { 40 | "@babel/core": "^7.9.0", 41 | "@types/react-redux": "^7.1.7", 42 | "@types/redux-saga": "^0.10.5", 43 | "es6-symbol": "^3.1.3" 44 | }, 45 | "dependencies": { 46 | "es6-symbol": "*", 47 | "@dxjs/shared": "^2.0.5", 48 | "invariant": "^2.2.4", 49 | "reflect-metadata": "^0.1.13" 50 | }, 51 | "peerDependencies": { 52 | "es6-symbol": "*" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/react-app-todolist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-app-todolist", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --config webpack.config.js", 9 | "preinstall": "echo '请使用 yarn 安装依赖'" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/core": "^7.11.1", 16 | "@babel/plugin-proposal-class-properties": "^7.10.4", 17 | "@babel/plugin-proposal-decorators": "^7.10.5", 18 | "@babel/plugin-transform-runtime": "^7.11.0", 19 | "@babel/preset-env": "^7.11.0", 20 | "@babel/preset-react": "^7.10.4", 21 | "@babel/preset-typescript": "^7.10.4", 22 | "@types/react-dom": "^16.9.8", 23 | "babel-loader": "^8.1.0", 24 | "css-loader": "^4.2.1", 25 | "eslint": "^7.6.0", 26 | "html-webpack-plugin": "^4.3.0", 27 | "style-loader": "^1.2.1", 28 | "ts-loader": "^8.0.2", 29 | "typescript": "^3.9.7", 30 | "webpack": "^4.44.1", 31 | "webpack-cli": "^3.3.12", 32 | "webpack-dev-server": "^3.11.0" 33 | }, 34 | "dependencies": { 35 | "@dxjs/common": "../../packages/common", 36 | "@dxjs/core": "../../packages/core", 37 | "immer": "^7.0.7", 38 | "react": "^16.13.1", 39 | "react-dom": "^16.13.1", 40 | "react-redux": "^7.2.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/pages/main/index.module.css: -------------------------------------------------------------------------------- 1 | 2 | .container { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | padding-top: 50px; 7 | } 8 | 9 | .input { 10 | outline: none; 11 | border: none; 12 | font-size: 16px; 13 | box-shadow: 0 0 30px #ddd; 14 | /* border-radius: 10px; */ 15 | width: 320px; 16 | height: 48px; 17 | padding: 10px 30px; 18 | } 19 | 20 | .button { 21 | width: 100px; 22 | padding: 0; 23 | margin: 0; 24 | border: none; 25 | outline: none; 26 | font-size: 16px; 27 | height: 68px; 28 | text-align: center; 29 | background: darkcyan; 30 | color: aliceblue; 31 | border-left: 1px solid #ddd; 32 | cursor: pointer; 33 | } 34 | 35 | .button:active { 36 | background: lightseagreen; 37 | } 38 | 39 | .list-container { 40 | width: 580px; 41 | list-style: none; 42 | margin: 0; 43 | padding: 30px 0 0; 44 | } 45 | 46 | .list-item { 47 | margin: 0; 48 | padding: 0; 49 | background: #efefef; 50 | margin-bottom: 10px; 51 | height: 48px; 52 | line-height: 48px; 53 | box-sizing: border-box; 54 | padding-left: 20px; 55 | color: #333; 56 | font-size: 14px; 57 | cursor: pointer; 58 | -moz-user-select: none; 59 | -webkit-user-select: none; 60 | white-space: nowrap; 61 | overflow: hidden; 62 | text-overflow: ellipsis; 63 | } 64 | 65 | .list-item:hover { 66 | background-color: lightseagreen; 67 | color: #eee; 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-reducer/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import { REDUCER_METHODS_KEY, SymbolType } from '@dxjs/shared/symbol'; 3 | import { Reducer, AnyAction } from 'redux'; 4 | import { reducerHook } from './hooks/reducer'; 5 | import { beforeReducerHook } from './hooks/before-reducer'; 6 | import { afterReducerHook } from './hooks/after-reducer'; 7 | import { DxModel, DxModelContstructor } from '../../dx-model/model'; 8 | 9 | export function createReducer(model: DxModel): Reducer | void { 10 | const Model = model.constructor as DxModelContstructor; 11 | 12 | // 获取 Model 中所有的 reducers 13 | // Map 14 | const reducers = Reflect.getMetadata(REDUCER_METHODS_KEY, Model); 15 | if (!reducers || !reducers.length) return; 16 | 17 | const map = new Map(reducers); 18 | const reducerEnhancer = reducerHook(); 19 | return reducerEnhancer( 20 | (state: T, action: AnyAction): T => { 21 | model.state = state || model.state; 22 | const methodName = map.get(action.type); 23 | if (!methodName) return model.state as T; 24 | beforeReducerHook(state, action, model); 25 | const reducer = Reflect.get(model, methodName); 26 | const newState = reducer.call(model, action.payload, model.state, action); 27 | afterReducerHook(newState, action, model); 28 | return newState ?? model.state; 29 | }, 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/react-app-todolist/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | 5 | /** 6 | * @type {import('webpack').Configuration} 7 | */ 8 | module.exports = { 9 | entry: './src/index.tsx', 10 | 11 | output: { 12 | path: path.join(__dirname, './dist'), 13 | filename: 'main.js', 14 | }, 15 | 16 | mode: 'development', 17 | 18 | resolve: { 19 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 20 | }, 21 | 22 | devServer: { 23 | host: 'localhost', 24 | port: 2020, 25 | hotOnly: true, 26 | }, 27 | 28 | devtool: 'cheap-module-source-map', 29 | 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.(js|jsx)$/, 34 | include: path.join(__dirname, './src'), 35 | loader: require.resolve('babel-loader'), 36 | }, 37 | { 38 | test: /\.(ts|tsx)$/, 39 | loader: require.resolve('babel-loader'), 40 | }, 41 | { 42 | test: /\.css$/, 43 | loaders: [ 44 | 'style-loader', 45 | { 46 | loader: 'css-loader', 47 | options: { 48 | modules: true, 49 | }, 50 | }, 51 | ], 52 | }, 53 | ], 54 | }, 55 | 56 | plugins: [ 57 | new HtmlWebpackPlugin({ 58 | title: 'REACT APP TODOLIST', 59 | }), 60 | new webpack.DefinePlugin({ 61 | __DEV__: 'true', 62 | }), 63 | ], 64 | }; 65 | -------------------------------------------------------------------------------- /packages/common/src/expand/effect.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | /* 3 | * @Author: 欧阳鑫 4 | * @Date: 2020-08-10 19:21:08 5 | * @Last Modified by: 欧阳鑫 6 | * @Last Modified time: 2020-08-11 16:21:28 7 | */ 8 | 9 | import { mark } from '../mark'; 10 | import { EFFECT_METHODS_KEY, EFFECT_METHODS_META, EFFECT_HELPERS, TAKE_EVERY, SymbolType } from '@dxjs/shared/symbol'; 11 | 12 | export function Effect(actionTypeArg?: SymbolType, helperTypeArg?: any, ...args: any[]): MethodDecorator { 13 | return (target: any, key: SymbolType, descriptor: TypedPropertyDescriptor): TypedPropertyDescriptor => { 14 | /** 15 | * 参数替换: 16 | * @Effect() 17 | * @Effect(TakeEvery) 18 | * @Effect("actionType", TakeEvery) 19 | * @Effect(Throttle, 350) 20 | * @Effect("actionType", Throttle, 350) 21 | */ 22 | const _defaultActionType = Symbol(String(key)); 23 | let _actionType = actionTypeArg ?? _defaultActionType; 24 | let _helperType = helperTypeArg ?? TAKE_EVERY; 25 | 26 | if (EFFECT_HELPERS.includes(actionTypeArg!)) { 27 | if (helperTypeArg) args.unshift(helperTypeArg); 28 | [_actionType, _helperType] = [_defaultActionType, actionTypeArg!]; 29 | } 30 | 31 | mark(EFFECT_METHODS_KEY, key)(target.constructor); 32 | mark(EFFECT_METHODS_META, { name: key, value: args, helperType: _helperType, actionType: _actionType })( 33 | target.constructor, 34 | ); 35 | 36 | return descriptor ?? target; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/combin-saga.ts: -------------------------------------------------------------------------------- 1 | import { ForkEffect, call } from 'redux-saga/effects'; 2 | import { createEffectHandler } from './create-effect-handler'; 3 | import { createEffectFork } from './create-effect-fork'; 4 | import { AnyAction } from 'redux'; 5 | import beforeEffectHook from './hooks/beforeEffect'; 6 | import { createEffectContext } from './create-effect-context'; 7 | import effectHook from './hooks/effect'; 8 | import afterEffectHook from './hooks/afterEffect'; 9 | import { DxModel } from '../../dx-model/model'; 10 | import { EffectTypeInterface } from './index'; 11 | 12 | export function combinSaga(model: DxModel, meta: EffectTypeInterface): ForkEffect { 13 | function* effect(action: AnyAction): Generator { 14 | const baseContext = createEffectContext(model, meta, action); 15 | 16 | const beforeHookContext = { 17 | abort: false, 18 | ...baseContext, 19 | }; 20 | 21 | // 判断 context 是否中断 22 | yield call(beforeEffectHook, beforeHookContext); 23 | if (beforeHookContext.abort) return; 24 | 25 | // 组合 promise 26 | const effect = createEffectHandler(model, meta); 27 | try { 28 | // 运行 effect 29 | const data = yield* effectHook(baseContext, effect(action)); 30 | //钩子 31 | afterEffectHook({ ...baseContext, data, error: null }); 32 | } catch (error) { 33 | afterEffectHook({ ...baseContext, data: null, error: error }); 34 | throw error; 35 | } 36 | } 37 | 38 | return createEffectFork(meta, effect); 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-effect/hooks/effect.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../../../helper/store'; 2 | 3 | function effectHook(context: any, iterator: Generator): Generator { 4 | const hook = 5 | (store.plugins.get('effect') as (( 6 | context: any, 7 | ref: { 8 | next: () => Generator; 9 | abort: (value: unknown) => void; 10 | throw: (value: unknown) => void; 11 | isDone: () => void; 12 | }, 13 | ) => Generator)[]) ?? []; 14 | 15 | // 在 effect 上加一层包装 16 | return hook.reduce((effect, currentHook) => { 17 | let effectRef: unknown = undefined; 18 | let effectValue: unknown = undefined; 19 | 20 | function $throw(value: unknown): void { 21 | effect.throw(value); 22 | } 23 | 24 | function abort(value: unknown): void { 25 | effect.return(value); 26 | } 27 | 28 | function* next(): Generator { 29 | effectRef = effect.next(effectValue); 30 | effectValue = yield (effectRef as IteratorResult).value; 31 | return effectValue; 32 | } 33 | 34 | function isDone(): boolean { 35 | if (effectRef === undefined) return false; 36 | return (effectRef as IteratorResult).done ?? true; 37 | } 38 | 39 | const hookIterator = currentHook(context, { next, abort, throw: $throw, isDone }); 40 | 41 | function* run(args?: unknown): Generator { 42 | const result = hookIterator.next(args); 43 | while (!result.done) { 44 | yield* run(result.value); 45 | } 46 | } 47 | 48 | return run(); 49 | }, iterator); 50 | } 51 | 52 | export default effectHook; 53 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-store/store-model.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../../helper/store'; 2 | import { DxModelContstructor, isDxModel } from '../../dx-model/model'; 3 | import { MODEL_NAME } from '@dxjs/shared/symbol'; 4 | import { CreateOption } from '../exports/create'; 5 | const invariant = require('invariant'); 6 | 7 | /** 8 | * 将 options 中传入的 models 处理 9 | * @param options 10 | */ 11 | export function storeModel(options: CreateOption): void { 12 | const models = store.getModels(); 13 | 14 | /** 15 | * 将 Modal 的构造类存起来 16 | * @param Model 17 | */ 18 | function collectModel(Model: DxModelContstructor, name?: string): void { 19 | if (__DEV__) { 20 | invariant(isDxModel(Model), '%s 不是一个有效的 DxModel, ', Model?.name ?? typeof Model); 21 | } 22 | const realName = name || Model.namespace; 23 | 24 | Reflect.defineMetadata(MODEL_NAME, realName, Model); 25 | models.map[realName] = Model; 26 | models.set.add(Model); 27 | } 28 | 29 | function modelsWithObject(withObject?: { [key: string]: DxModelContstructor }): void { 30 | if (typeof withObject === 'undefined') return; 31 | if (__DEV__) { 32 | invariant(typeof withObject === 'object', 'models 不是一个有效的类型 %s, 请传入数组或对象', typeof withObject); 33 | } 34 | Object.keys(withObject).forEach(key => collectModel(withObject[key], key)); 35 | } 36 | 37 | if (Array.isArray(options.models)) { 38 | let ModelConstructor; 39 | while ((ModelConstructor = options?.models.shift())) { 40 | if (isDxModel(ModelConstructor)) { 41 | collectModel(ModelConstructor); 42 | } 43 | } 44 | } else { 45 | modelsWithObject(options.models); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-action.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { store } from '../helper/store'; 3 | import { REDUCER_METHODS_KEY, EFFECT_METHODS_META } from '@dxjs/shared/symbol'; 4 | import { AnyAction } from 'redux'; 5 | import { Dispatch } from 'react'; 6 | 7 | interface Effect { 8 | name: string; 9 | actionType: string; 10 | } 11 | 12 | export type DxActionCreate = (payload?: Partial) => void; 13 | 14 | export function createAction(dispatch: Dispatch): void { 15 | const models = store.getModels(); 16 | models.set.forEach(Model => { 17 | const reducers = Reflect.getMetadata(REDUCER_METHODS_KEY, Model) as [symbol, string][]; 18 | const effects = Reflect.getMetadata(EFFECT_METHODS_META, Model) as Effect[]; 19 | 20 | const reducersMap = new Map(reducers); 21 | if (reducersMap && reducersMap.size) { 22 | reducersMap.forEach((methodName, actionType) => { 23 | Reflect.set(Model, methodName, function action(payload: any, autoDispatch?: boolean): AnyAction | void { 24 | if (autoDispatch === false) { 25 | return { type: actionType, payload }; 26 | } 27 | return dispatch({ type: actionType, payload }); 28 | }); 29 | }); 30 | } 31 | 32 | if (effects && effects.length) { 33 | effects.forEach(({ name, actionType }) => { 34 | Reflect.set(Model, name, function action(payload: any, autoDispatch?: boolean): AnyAction | void { 35 | if (autoDispatch === false) { 36 | return { type: actionType, payload }; 37 | } 38 | return dispatch({ type: actionType, payload }); 39 | }); 40 | }); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /examples/taro-sample/src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { Component, Config } from '@tarojs/taro'; 2 | import { View, Button, Input, Text } from '@tarojs/components'; 3 | import { connect } from '@tarojs/redux'; 4 | import { Dx } from '@dxjs/core'; 5 | import './index.css'; 6 | 7 | interface IndexProps { 8 | todoList: string[]; 9 | } 10 | 11 | const mapStateToProps = ({ TodolistModel }) => { 12 | return { todoList: TodolistModel.data }; 13 | }; 14 | 15 | @connect(mapStateToProps) 16 | export default class Index extends Component { 17 | config: Config = { 18 | navigationBarTitleText: '首页', 19 | }; 20 | 21 | state = { 22 | inputValue: '', 23 | }; 24 | 25 | onChangeValue = ({ target }) => { 26 | this.setState({ inputValue: target.value }); 27 | }; 28 | 29 | onPressSyncButton = () => { 30 | Dx.getModels('TodolistModel').addToData(this.state.inputValue, true); 31 | }; 32 | 33 | onPressASyncButton = () => { 34 | Dx.getModels('To').asyncAddToData(this.state.inputValue, true); 35 | }; 36 | 37 | render() { 38 | const { inputValue } = this.state; 39 | const { todoList } = this.props; 40 | return ( 41 | 42 | 48 | 51 | 54 | 55 | {todoList.map(text => ( 56 | {text} 57 | ))} 58 | 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/core/src/dx/exports/models.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { store } from '../../helper/store'; 3 | import { MODEL_NAME } from '@dxjs/shared/symbol'; 4 | import { DxModelContstructor } from '../../dx-model/model'; 5 | 6 | export type GetModels = DxModelContstructor | DxModelContstructor[] | { [key: string]: DxModelContstructor } | undefined; 7 | 8 | // eslint-disable-next-line @typescript-eslint/explicit-function-return-type 9 | export function modelsFactory() { 10 | function getModels(): { [key: string]: DxModelContstructor }; 11 | function getModels(match: RegExp): DxModelContstructor[]; 12 | function getModels(match: string): DxModelContstructor; 13 | 14 | function getModels(match?: string | RegExp): GetModels { 15 | const map = store.getModels()?.map || {}; 16 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 17 | const set = store.getModels()?.set || new Set(); 18 | if (!match) return map; 19 | if (__DEV__) { 20 | require('invariant')( 21 | ['string', 'undefined'].some(type => typeof match === type) || match instanceof RegExp, 22 | '请传入有效的参数,当前参数类型为 %s, 但只接受 string、undefined、regexp 的类型', 23 | typeof match, 24 | ); 25 | require('invariant')(store.reduxStore, 'store 还没有创建,请先调用 Dx.createStore 或 Dx.create 创建 store'); 26 | } 27 | 28 | function getModelName(model: DxModelContstructor): string { 29 | return Reflect.getMetadata(MODEL_NAME, model) ?? model.name; 30 | } 31 | 32 | if (typeof match === 'string') { 33 | if (Reflect.has(map, match)) return Reflect.get(map, match); 34 | return [...set].find(model => getModelName(model).startsWith(match)); 35 | } 36 | 37 | return [...set].filter(model => match.test(getModelName(model))); 38 | } 39 | return getModels; 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxjs/core", 3 | "version": "2.0.5", 4 | "description": "Unified management of your application status, use enhancer to deal with other tedious things", 5 | "keywords": [ 6 | "dxjs", 7 | "react-redux", 8 | "react-saga", 9 | "react-dxjs", 10 | "react", 11 | "saga", 12 | "redux", 13 | "model" 14 | ], 15 | "author": "mro <254225961@qq.com>", 16 | "homepage": "https://github.com/taixw2/dx#readme", 17 | "license": "MIT", 18 | "main": "index.js", 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://registry.npmjs.org/" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/taixw2/dx.git" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/taixw2/dx/issues" 29 | }, 30 | "types": "./src/index.ts", 31 | "files": [ 32 | "LICENSE", 33 | "README.md", 34 | "index.js", 35 | "cjs", 36 | "umd", 37 | "src" 38 | ], 39 | "devDependencies": { 40 | "@babel/core": "^7.9.0", 41 | "@chores/tsconfig": "^1.0.1", 42 | "@types/invariant": "^2.2.31", 43 | "@types/koa-compose": "^3.2.5", 44 | "@types/react": "^16.9.29", 45 | "@types/react-redux": "^7.1.7" 46 | }, 47 | "dependencies": { 48 | "@dxjs/common": "^2.0.5", 49 | "@dxjs/shared": "^2.0.5", 50 | "es6-symbol": "^3.1.3", 51 | "invariant": "^2.2.4", 52 | "koa-compose": "^4.1.0", 53 | "react-redux": "^7.2.0", 54 | "redux": "^4.0.5", 55 | "redux-saga": "^1.1.3", 56 | "reflect-metadata": "^0.1.13" 57 | }, 58 | "peerDependencies": { 59 | "es6-symbol": "*", 60 | "invariant": "*", 61 | "koa-compose": "*", 62 | "react": "*", 63 | "react-dom": "*", 64 | "react-redux": "*", 65 | "redux": "*", 66 | "redux-saga": "*", 67 | "reflect-metadata": "*" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/react-app-todolist/src/pages/main/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 3 | import React from 'react'; 4 | import { Dx } from '@dxjs/core'; 5 | import styles from './index.module.css'; 6 | import { connect } from '@dxjs/core'; 7 | 8 | function Item({ text, index }: any) { 9 | function onPressItem() { 10 | Dx.getModels(/^Todo/)[0].removeToData(index, true); 11 | } 12 | return ( 13 |
  • 14 | {text} 15 |
  • 16 | ); 17 | } 18 | 19 | function ListContainer({ data }: any) { 20 | if (!data || !data.length) return null; 21 | return ( 22 |
      23 | {data.map((text: string, i: number) => ( 24 | 25 | ))} 26 |
    27 | ); 28 | } 29 | 30 | function Main({ todoList }: any) { 31 | const ref: any = React.useRef(); 32 | 33 | function onPressSyncButton() { 34 | Dx.getModels().TodolistModel.addToData(ref.current?.value, true); 35 | } 36 | 37 | function onPressAsyncButton() { 38 | Dx.getModels('T').asyncAddToData(ref.current.value, true); 39 | } 40 | 41 | return ( 42 |
    43 |
    44 | 45 | 48 | 51 |
    52 | 53 |
    54 | ); 55 | } 56 | 57 | const mapStateToProps = store => { 58 | return { todoList: store.TodolistModel.data }; 59 | }; 60 | 61 | export default connect(mapStateToProps)(Main); 62 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 当前脚本会在 pre-push 的时候执行, 3 | * 执行前判断 commit log 是否 release(release_type): xxx 的格式 4 | * 如果是 commit type 是 release 则修改 package.json 并且打个 tag 5 | */ 6 | 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const semver = require('semver'); 10 | const { sync } = require('conventional-commits-parser'); 11 | const defaultChangelogOpts = require('conventional-changelog-angular'); 12 | const cp = require('child_process'); 13 | 14 | const CWD = process.cwd(); 15 | const RELEASE_TYPE = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease']; 16 | 17 | function updateVersion(pkgPath) { 18 | if (!fs.existsSync(pkgPath)) return; 19 | 20 | const releaseType = [...process.argv].pop(); 21 | if (!RELEASE_TYPE.includes(releaseType)) return; 22 | 23 | const packageJSON = JSON.parse(fs.readFileSync(pkgPath).toString('utf8')); 24 | const currentVersion = packageJSON.version; 25 | 26 | const nextVersion = semver.inc(currentVersion, releaseType); 27 | fs.writeFileSync(pkgPath, JSON.stringify({ ...packageJSON, version: nextVersion }, null, 2)); 28 | 29 | return nextVersion; 30 | } 31 | 32 | function updatePackageVersion() { 33 | fs.readdirSync('packages').forEach(name => { 34 | const dirname = path.join(CWD, 'packages', name); 35 | if (!fs.statSync(dirname).isDirectory) return; 36 | updateVersion(path.join(dirname, 'package.json')); 37 | }); 38 | } 39 | 40 | function updateGlobalVersion() { 41 | const version = updateVersion(path.join(CWD, 'package.json')); 42 | try { 43 | cp.execSync('git tag v' + version); 44 | } catch (error) { 45 | // 46 | } 47 | return version; 48 | } 49 | 50 | function run() { 51 | const version = updateGlobalVersion(); 52 | updatePackageVersion(); 53 | 54 | cp.execSync('npm run clog'); 55 | cp.execSync('git add package.json CHANGELOG.md'); 56 | cp.execSync('git add packages/**/package.json'); 57 | cp.execSync(`git commit -m "feat(release): release v${version}"`); 58 | } 59 | 60 | run(); 61 | -------------------------------------------------------------------------------- /examples/taro-sample/config/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | projectName: 'taro-sample', 3 | date: '2020-4-20', 4 | designWidth: 750, 5 | deviceRatio: { 6 | '640': 2.34 / 2, 7 | '750': 1, 8 | '828': 1.81 / 2 9 | }, 10 | sourceRoot: 'src', 11 | outputRoot: 'dist', 12 | babel: { 13 | sourceMap: true, 14 | presets: [ 15 | ['env', { 16 | modules: false 17 | }] 18 | ], 19 | plugins: [ 20 | 'transform-decorators-legacy', 21 | 'transform-object-rest-spread', 22 | ['transform-class-properties', { 23 | spec: true 24 | }], 25 | ['transform-runtime', { 26 | 'helpers': false, 27 | 'polyfill': false, 28 | 'regenerator': true, 29 | 'moduleName': 'babel-runtime' 30 | }] 31 | ] 32 | }, 33 | plugins: [], 34 | defineConstants: { 35 | }, 36 | mini: { 37 | postcss: { 38 | pxtransform: { 39 | enable: true, 40 | config: {} 41 | }, 42 | url: { 43 | enable: true, 44 | config: { 45 | limit: 10240 // 设定转换尺寸上限 46 | } 47 | }, 48 | cssModules: { 49 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 50 | config: { 51 | namingPattern: 'module', // 转换模式,取值为 global/module 52 | generateScopedName: '[name]__[local]___[hash:base64:5]' 53 | } 54 | } 55 | } 56 | }, 57 | h5: { 58 | publicPath: '/', 59 | staticDirectory: 'static', 60 | postcss: { 61 | autoprefixer: { 62 | enable: true, 63 | config: { 64 | browsers: [ 65 | 'last 3 versions', 66 | 'Android >= 4.1', 67 | 'ios >= 8' 68 | ] 69 | } 70 | }, 71 | cssModules: { 72 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 73 | config: { 74 | namingPattern: 'module', // 转换模式,取值为 global/module 75 | generateScopedName: '[name]__[local]___[hash:base64:5]' 76 | } 77 | } 78 | } 79 | }, 80 | alias: { 81 | "react": require.resolve("@tarojs/taro"), 82 | "react-dom": require.resolve("@tarojs/taro"), 83 | } 84 | } 85 | 86 | module.exports = function (merge) { 87 | if (process.env.NODE_ENV === 'development') { 88 | return merge({}, config, require('./dev')) 89 | } 90 | return merge({}, config, require('./prod')) 91 | } 92 | -------------------------------------------------------------------------------- /packages/core/src/dx/create-store/create-store.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../../helper/store'; 2 | import { Reducer, AnyAction, Store } from 'redux'; 3 | import { ForkEffect, spawn, all } from 'redux-saga/effects'; 4 | import { createReducer } from '../create-reducer'; 5 | import { createEffect } from '../create-effect'; 6 | import { MODEL_NAME } from '@dxjs/shared/symbol'; 7 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 8 | import reduxSaga from 'redux-saga'; 9 | import { promiseMiddleware } from './promise-middleware'; 10 | import { createAction } from '../create-action'; 11 | import { resolve } from '../../utils'; 12 | import { DxModel } from '../../dx-model/model'; 13 | import { CreateOption } from '../exports/create'; 14 | 15 | const nonp = (): void => undefined; 16 | 17 | const createSagaMiddleware = resolve.getExportFromNamespace(reduxSaga); 18 | 19 | /** 20 | * 重组 effect 和 reducer 21 | */ 22 | function recombinEffectAndReducer(): [{ [key: string]: Reducer }, ForkEffect[], DxModel[]] { 23 | const reducers: { [key: string]: Reducer } = {}; 24 | const effects: ForkEffect[] = []; 25 | const models: DxModel[] = []; 26 | store.getModels().set.forEach(ModelConstructor => { 27 | const model = new ModelConstructor(); 28 | const modelReducer = createReducer(model); 29 | const modelEffect = createEffect(model); 30 | 31 | const modelName = Reflect.getMetadata(MODEL_NAME, ModelConstructor); 32 | if (modelReducer) { 33 | reducers[modelName] = modelReducer; 34 | } 35 | if (modelEffect) { 36 | effects.push(spawn(modelEffect)); 37 | } 38 | 39 | models.push(model); 40 | }); 41 | 42 | return [reducers, effects, models]; 43 | } 44 | 45 | /** 46 | * 调用 redux 的 create store 47 | * 生成 redux store 48 | */ 49 | export function combinStore(options: CreateOption): Store { 50 | const optionsMiddlewares = options.middlewares ?? []; 51 | const sagaMiddleware = createSagaMiddleware({ 52 | onError: options.onSagaError, 53 | effectMiddlewares: options.sagaMiddlewares, 54 | }); 55 | 56 | const middlewares = [promiseMiddleware(), sagaMiddleware, ...optionsMiddlewares]; 57 | 58 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 59 | const store = createStore(nonp, applyMiddleware(...middlewares)); 60 | const [reducers, effects, models] = recombinEffectAndReducer(); 61 | 62 | store.replaceReducer(combineReducers({ __dxjs: () => '__dxjs', ...reducers })); 63 | 64 | sagaMiddleware.run(function*() { 65 | yield all(effects); 66 | }); 67 | 68 | createAction(store.dispatch); 69 | 70 | // 初始化 71 | models.forEach(model => model.init?.()); 72 | return store; 73 | } 74 | -------------------------------------------------------------------------------- /packages/shared/symbol.ts: -------------------------------------------------------------------------------- 1 | export type SymbolType = symbol | string; 2 | /** 3 | * 获取 model name 的唯一标识 4 | * model name 会放在 model 构造器中的 matedata 中 5 | * 以 model name 来关联对应的 Model 构造器 6 | * 7 | * Model Name 通常是构造器的名称,但是也可以设置别名 8 | */ 9 | export let MODEL_NAME: SymbolType = '__model_name'; 10 | 11 | /** 12 | * 用于获取作为 reducer 的 key, 13 | * 只有使用装饰器 @Reducer 表明的,才算一个有效 key 14 | */ 15 | export let RECORD_REDUCER_KEYS: SymbolType = '__record_reducer_keys'; 16 | 17 | /** 18 | * 用来标明 类 或者 方法 19 | * 可用于决定某个增强器是否适用于此类/方法 20 | */ 21 | export let LABEL: SymbolType = '__label'; 22 | 23 | /** 24 | * reducer 方法 25 | * 26 | * 用于获取类中的 reducer 方法,如 27 | * 28 | * (Reflect.getMetadata(Model, REDUCER_METHODS_KEY) as Map).get(action.type) 29 | */ 30 | export let REDUCER_METHODS_KEY: SymbolType = '__reducer_method'; 31 | 32 | /** 33 | * reducer 增强器, 34 | * 用于获取某个 reducer 的增强器 35 | */ 36 | export let REDUCER_ENHANCER_KEY: SymbolType = '__reducer_enhancer'; 37 | 38 | /** 39 | * effect 的 helper 别名 40 | */ 41 | export let TAKE_LATEST: SymbolType = '__take_latest'; 42 | export let TAKE_EVERY: SymbolType = '__take_every'; 43 | export let TAKE_LEADING: SymbolType = '__take_leading'; 44 | export let THROTTLE: SymbolType = '__throttle'; 45 | 46 | /** 47 | * effect 方法 48 | * 49 | * 用于获取类中的 effect 方法,如 50 | * 51 | * (Reflect.getMetadata(Model, EFFECT_METHODS_KEY) as Map) 52 | */ 53 | 54 | export let EFFECT_METHODS_KEY: SymbolType = '__effect_method_key'; 55 | 56 | export let EFFECT_METHODS_META: SymbolType = '__effect_method_meta'; 57 | 58 | export let GUARD_KEY: SymbolType = '__guard_key'; 59 | export let SENTINEL_KEY: SymbolType = '__sentinel_key'; 60 | export let DISGUISER_KEY: SymbolType = '__disguiser_key'; 61 | 62 | if (typeof Symbol === 'function' && Symbol.for) { 63 | MODEL_NAME = Symbol.for('__model_name'); 64 | RECORD_REDUCER_KEYS = Symbol.for('__record_reducer_keys'); 65 | LABEL = Symbol.for('__label'); 66 | REDUCER_METHODS_KEY = Symbol.for('__reducer_method'); 67 | REDUCER_ENHANCER_KEY = Symbol.for('__reducer_enhancer'); 68 | TAKE_LATEST = Symbol.for('__take_latest'); 69 | TAKE_EVERY = Symbol.for('__take_every'); 70 | TAKE_LEADING = Symbol.for('__take_leading'); 71 | THROTTLE = Symbol.for('throttle'); 72 | EFFECT_METHODS_KEY = Symbol.for('__effect_method_key'); 73 | EFFECT_METHODS_META = Symbol.for('__effect_method_meta'); 74 | GUARD_KEY = Symbol.for('__guard_key'); 75 | SENTINEL_KEY = Symbol.for('__sentinel_key'); 76 | DISGUISER_KEY = Symbol.for('__disguiser_key'); 77 | } 78 | 79 | export const EFFECT_HELPERS: SymbolType[] = [TAKE_LATEST, TAKE_EVERY, TAKE_LEADING, THROTTLE]; 80 | 81 | export const DISGUISER_IO = '@dxjs/IO'; 82 | export const DISGUISER_NEXT = 'NEXT'; 83 | export const DISGUISER_ABORT = 'ABORT'; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dxjs", 3 | "version": "2.0.5", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "workspaces": [ 8 | "packages/*" 9 | ], 10 | "scripts": { 11 | "docs:dev": "vuepress dev docs", 12 | "docs:build": "vuepress build docs", 13 | "test": "jest --config scripts/jest/jest.config.js", 14 | "test:cov": "jest --config scripts/jest/jest.config.js --coverage", 15 | "build": "node scripts/build.js && tsc -p tsconfig.dts.json", 16 | "release": "node scripts/release.js", 17 | "clog": "conventional-changelog -p angular -i CHANGELOG.md -s" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "@babel/core": "^7.9.0", 24 | "@babel/plugin-proposal-class-properties": "^7.8.3", 25 | "@babel/plugin-proposal-decorators": "^7.8.3", 26 | "@babel/plugin-transform-runtime": "^7.9.0", 27 | "@babel/preset-env": "^7.9.5", 28 | "@babel/preset-typescript": "^7.9.0", 29 | "@chores/tsconfig": "^1.0.1", 30 | "@commitlint/cli": "^8.3.5", 31 | "@commitlint/config-angular": "^8.3.4", 32 | "@commitlint/config-conventional": "^8.3.4", 33 | "@types/jest": "^25.2.1", 34 | "@types/react": "^16.9.34", 35 | "@typescript-eslint/eslint-plugin": "^2.26.0", 36 | "@typescript-eslint/parser": "^2.26.0", 37 | "babel-eslint": "^10.1.0", 38 | "babel-jest": "^25.3.0", 39 | "chalk": "^4.0.0", 40 | "codecov": "^3.6.5", 41 | "conventional-changelog-angular": "^5.0.6", 42 | "conventional-changelog-cli": "^2.0.31", 43 | "conventional-commits-parser": "^3.0.8", 44 | "eslint": "^6.8.0", 45 | "eslint-config-airbnb": "^18.0.1", 46 | "eslint-config-prettier": "^6.10.1", 47 | "eslint-plugin-jsx-control-statements": "^2.2.1", 48 | "eslint-plugin-prettier": "^3.1.2", 49 | "gzip-size": "^5.1.1", 50 | "husky": "^4.2.5", 51 | "invariant": "^2.2.4", 52 | "jest": "^25.3.0", 53 | "lerna": "^3.16.5", 54 | "lint-staged": "^10.1.6", 55 | "mkdirp": "^1.0.4", 56 | "ncp": "^2.0.0", 57 | "prettier": "^1.19.1", 58 | "react": "^16.13.1", 59 | "react-dom": "^16.13.1", 60 | "redux": "^4.0.5", 61 | "redux-saga": "^1.1.3", 62 | "rimraf": "^3.0.2", 63 | "rmfr": "^2.0.0", 64 | "rollup": "^2.6.1", 65 | "rollup-plugin-babel": "^4.4.0", 66 | "rollup-plugin-commonjs": "^10.1.0", 67 | "rollup-plugin-node-resolve": "^5.2.0", 68 | "rollup-plugin-replace": "^2.2.0", 69 | "rollup-plugin-terser": "^5.3.0", 70 | "rollup-plugin-typescript2": "^0.27.1", 71 | "semver": "^7.3.2", 72 | "typescript": "^3.8.3", 73 | "vuepress": "^1.4.0" 74 | }, 75 | "dependencies": {}, 76 | "husky": { 77 | "hooks": { 78 | "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS", 79 | "pre-commit": "lint-staged" 80 | } 81 | }, 82 | "lint-staged": { 83 | "packages/**/*.{js,ts}": [ 84 | "prettier —-write", 85 | "eslint --fix", 86 | "git add" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/taro-sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taro-sample1", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "dxjs example for taro", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": true, 9 | "css": "sass" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:quickapp": "taro build --type quickapp", 20 | "dev:weapp": "npm run build:weapp -- --watch", 21 | "dev:swan": "npm run build:swan -- --watch", 22 | "dev:alipay": "npm run build:alipay -- --watch", 23 | "dev:tt": "npm run build:tt -- --watch", 24 | "dev:h5": "npm run build:h5 -- --watch", 25 | "dev:rn": "npm run build:rn -- --watch", 26 | "dev:qq": "npm run build:qq -- --watch", 27 | "dev:quickapp": "npm run build:quickapp -- --watch" 28 | }, 29 | "author": "", 30 | "license": "MIT", 31 | "dependencies": { 32 | "@dxjs/common": "^1.0.2", 33 | "@dxjs/core": "^1.0.2", 34 | "@tarojs/components": "2.1.5", 35 | "@tarojs/components-qa": "2.1.5", 36 | "@tarojs/redux": "^2.1.5", 37 | "@tarojs/redux-h5": "^2.1.5", 38 | "@tarojs/router": "2.1.5", 39 | "@tarojs/taro": "2.1.5", 40 | "@tarojs/taro-alipay": "2.1.5", 41 | "@tarojs/taro-h5": "2.1.5", 42 | "@tarojs/taro-qq": "2.1.5", 43 | "@tarojs/taro-quickapp": "2.1.5", 44 | "@tarojs/taro-rn": "2.1.5", 45 | "@tarojs/taro-swan": "2.1.5", 46 | "@tarojs/taro-tt": "2.1.5", 47 | "@tarojs/taro-weapp": "2.1.5", 48 | "babel-runtime": "^6.26.0", 49 | "nerv-devtools": "^1.5.5", 50 | "nervjs": "^1.5.5", 51 | "regenerator-runtime": "0.11.1" 52 | }, 53 | "devDependencies": { 54 | "@tarojs/mini-runner": "2.1.5", 55 | "@tarojs/webpack-runner": "2.1.5", 56 | "@types/react": "^16.4.6", 57 | "@types/webpack-env": "^1.13.6", 58 | "@typescript-eslint/eslint-plugin": "^2.13.0", 59 | "@typescript-eslint/parser": "^2.13.0", 60 | "babel-eslint": "^8.2.3", 61 | "babel-plugin-transform-class-properties": "^6.24.1", 62 | "babel-plugin-transform-decorators": "^6.24.1", 63 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 64 | "babel-plugin-transform-jsx-stylesheet": "^0.6.5", 65 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 66 | "babel-plugin-transform-runtime": "^6.23.0", 67 | "babel-preset-env": "^1.6.1", 68 | "babylon": "7.0.0-beta.30", 69 | "eslint": "^5.16.0", 70 | "eslint-config-taro": "2.1.5", 71 | "eslint-plugin-import": "^2.12.0", 72 | "eslint-plugin-react": "^7.8.2", 73 | "eslint-plugin-react-hooks": "^1.6.1", 74 | "eslint-plugin-taro": "2.1.5", 75 | "stylelint": "9.3.0", 76 | "stylelint-config-taro-rn": "2.1.5", 77 | "stylelint-taro-rn": "2.1.5", 78 | "typescript": "^3.0.1" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dxjs 2 | 3 | [![Codecov Coverage](https://img.shields.io/codecov/c/github/taixw2/dx/master.svg?style=flat-square)](https://codecov.io/gh/taixw2/dx/) 4 | [![NPM Version](https://img.shields.io/npm/v/@dxjs/core?style=flat)](https://npmjs.com/package/@dxjs/core) 5 | [![Build Release](https://github.com/taixw2/dxjs/workflows/Release/badge.svg)](https://github.com/taixw2/dxjs/workflows/Release/badge.svg) 6 | 7 | 基于 Redux、React-Redux、Redux-saga、Typescripe 的状态管理库 8 | 9 | ### Dxjs 能做什么 10 | 11 | Dxjs 是一个 基于 redux-saga 和 typescript 的状态管理库,它在 redux-saga 的基础上新增了一些钩子,让你能够在数据流的任意阶段插入逻辑, 12 | 13 | ![](./docs/images/saga-process.jpg) 14 | 15 | 16 | ### 基础特性 17 | 18 | - **Symbol**, 解决 Action Type 冲突,通过方法名换取 Action, 免去定义烦恼 19 | - **基于 Class**, 更多特性可用:私有属性、装饰器、继承 20 | - **Typescript**: 减少类型错误,增强代码的鲁棒性 21 | - **增强器**: 各个阶段植入逻辑,减少模板代码 22 | 23 | ### 安装 24 | 25 | ```sh 26 | npm install react-redux @dxjs/core @dxjs/common 27 | 28 | # 采用 yarn 29 | yarn add react-redux @dxjs/core @dxjs/common 30 | ``` 31 | 32 | ### 在 taro 中安装依赖 33 | 34 | ```sh 35 | npm install @tarojs/redux @tarojs/redux-h5 36 | 37 | # 在 config/index.js 中设置 38 | alias: { 39 | "react": require.resolve("@tarojs/taro"), 40 | "react-dom": require.resolve("@tarojs/taro"), 41 | "react-redux": require.resolve("@tarojs/redux"), 42 | } 43 | ``` 44 | 45 | ### DxModel 46 | 47 | ```typescript 48 | 49 | export class ExampleModel extends DxModel { 50 | state = { count: 1 }; 51 | 52 | @Reducer('ns/count') 53 | coustActionReducer(count: number): void { 54 | this.state.count += count; 55 | } 56 | 57 | @Reducer() 58 | updateCount(count: number): void { 59 | this.state.count += count; 60 | } 61 | 62 | @Effect('ns/asyncCount') 63 | *asyncUpdateCount(payload: number): Generator { 64 | const count = yield this.$call(delayGet, payload); 65 | yield this.$put((ExampleModel as DxModelContstructor).updateCount(count)); 66 | } 67 | 68 | @Effect() 69 | *delayUpdate(payload: number): Generator { 70 | const count = yield this.$delay(1000, payload); 71 | yield this.$put((ExampleModel as DxModelContstructor).updateCount(count)); 72 | return (count as number) + 10; 73 | } 74 | } 75 | 76 | ``` 77 | 78 | ### 快速开始 79 | 80 | **创建一个 Dx 实例** 81 | 82 | > 为了方便,create 会返回一个 React Component, 如果不想返回 Component, 可以使用 Dx.createStore 83 | ```typescript 84 | import { Dx } from "@dxjs/core" 85 | 86 | const DxApp = Dx.create({ 87 | models: [], 88 | reducerEnhancer: [], 89 | }) 90 | ``` 91 | 92 | **创建 model** 93 | 94 | > 只需要继承 DxModel, 你可以通过 Dx.collect 来收集 model, 或者手动传入到 Dx.create 中 95 | 96 | ```typescript 97 | import { Dx, DxModel } from "@dxjs/core" 98 | 99 | @Dx.collect() 100 | class UserModel extends DxModel {} 101 | ``` 102 | 103 | 104 | **定义 reducer 和 effect** 105 | 106 | > 利用装饰起 Effect 和 Reducer 来标识这是一个 effect 还是 reducer, 他们还接受一些参数 107 | ```typescript 108 | import { Dx, DxModel } from "@dxjs/core" 109 | import { Effect, Reducer } from "@dxjs/core" 110 | 111 | @Dx.collect() 112 | class UserModel extends DxModel { 113 | @Reducer() 114 | update(payload) { 115 | return { payload } 116 | } 117 | 118 | @Effect() 119 | update(payload) { 120 | this.$call(service) 121 | } 122 | } 123 | ``` 124 | 125 | **获取Action** 126 | 127 | 通过 Dx.getModels 获取到所有的 model,注意获取到的都是构造函数,只是内部把所有的 effect 和 reducer 都挂在了静态方法上,所以看起来像直接调用了实例方法一样,实际上第二个参数设为 `true` 会直接调用 dispatch, 否则则会返回 action 128 | 129 | ```typescript 130 | import { Dx } from "@dxjs/core" 131 | Dx.getModels().UserModel.update("some", true) 132 | Dx.getModels('UserModel').update("some", true) 133 | Dx.getModels('Use').update("some", true) 134 | Dx.getModels(/^Use/)[0].update("some", true) 135 | 136 | ``` 137 | 138 | **开始** 139 | 140 | 以上是一些基础功能,更多详细的介绍你需要更完整的查看文档:[文档入口](https://dxjs.fun),接下来则开始准备使用吧。 141 | 142 | ### example 143 | 144 | 我还为大家准备了一些简单的 example: 145 | 146 | - [sample \(by create-react-app\)](./examples/create-react-app) 147 | - [sample2 \(by taro\)](./examples/taro-sample) 148 | 149 | ## 欢迎 Star、Pull Request、Issues、补充文档 150 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | const path = require('path'); 3 | const chalk = require('chalk'); 4 | const rmfr = require('rmfr'); 5 | const ncp = require('ncp').ncp; 6 | const mkdirp = require('mkdirp'); 7 | const fs = require('fs'); 8 | const babel = require('rollup-plugin-babel'); 9 | const commonjs = require('rollup-plugin-commonjs'); 10 | const resolve = require('rollup-plugin-node-resolve'); 11 | // const resolve = require('rollup-plugin-node-resolve'); 12 | const rollupTypescript = require('rollup-plugin-typescript2'); 13 | const terser = require('rollup-plugin-terser').terser; 14 | const replace = require('rollup-plugin-replace'); 15 | const gzip = require('gzip-size'); 16 | const babelCore = require('@babel/core'); 17 | const bundles = require('./bundles'); 18 | 19 | const cwd = process.cwd(); 20 | 21 | function getPackgeFileName(packageName, bundleType) { 22 | switch (bundleType) { 23 | case bundles.CJS: 24 | case bundles.UMD: 25 | return `${packageName}.production.min.js`; 26 | case bundles.UMD_DEV: 27 | case bundles.CJS_DEV: 28 | return `${packageName}.development.js`; 29 | default: 30 | // 31 | break; 32 | } 33 | } 34 | 35 | function getFormat(bundleType) { 36 | switch (bundleType) { 37 | case bundles.UMD_DEV: 38 | case bundles.UMD: 39 | return 'umd'; 40 | case bundles.CJS: 41 | case bundles.CJS_DEV: 42 | return 'cjs'; 43 | default: 44 | // 45 | break; 46 | } 47 | return 'umd'; 48 | } 49 | 50 | function getOutoutPath(packageName, bundleType, filename) { 51 | return `build/${packageName}/${getFormat(bundleType)}/${filename}`; 52 | } 53 | 54 | function getNodeEnv(bundleType) { 55 | switch (bundleType) { 56 | case bundles.CJS: 57 | case bundles.UMD: 58 | return 'production'; 59 | case bundles.UMD_DEV: 60 | case bundles.CJS_DEV: 61 | return 'development'; 62 | default: 63 | // 64 | break; 65 | } 66 | } 67 | 68 | function combinGlobalModule(externals) { 69 | return externals.reduce((a, b) => ((a[b.entry] = b.global), a), {}); 70 | } 71 | 72 | function getPackageName(package) { 73 | return package.split('/')[1]; 74 | } 75 | 76 | /** 77 | * rollup build 78 | * @param {*} package 包 79 | * @param {*} bundleType 打包类型 80 | */ 81 | async function createBundle(package, bundleType) { 82 | // 获取文件名称 83 | const { entry, global: globalName, externals } = package; 84 | const packageName = getPackageName(entry); 85 | const packageFileName = getPackgeFileName(packageName, bundleType); 86 | const tag = chalk.white.bold(packageFileName) + chalk.dim(` (${bundleType})`); 87 | console.log(chalk.bgYellow.black(' BUILDING '), tag); 88 | 89 | process.env.NODE_ENV = getNodeEnv(bundleType); 90 | const isProduction = process.env.NODE_ENV === 'production'; 91 | 92 | 93 | try { 94 | const entryFile = require.resolve(entry); 95 | const bundle = await rollup.rollup({ 96 | input: entryFile, 97 | external: externals.map(v => v.entry), 98 | plugins: [ 99 | replace({ 100 | __DEV__: !isProduction, 101 | __ISSUE__: 'https://github.com/taixw2/dxjs/issues', 102 | }), 103 | commonjs(), 104 | resolve({ 105 | extensions: ['.js', '.ts'], 106 | }), 107 | rollupTypescript(), 108 | babel({ 109 | configFile: path.resolve('.babelrc'), 110 | exclude: 'node_modules/**', 111 | runtimeHelpers: true, 112 | extensions: [...babelCore.DEFAULT_EXTENSIONS, '.ts'], 113 | }), 114 | isProduction && terser(), 115 | ], 116 | }); 117 | 118 | await bundle.write({ 119 | // output option 120 | preferConst: true, 121 | file: getOutoutPath(packageName, bundleType, packageFileName), 122 | format: getFormat(bundleType), 123 | globals: combinGlobalModule(externals), 124 | exports: 'auto', 125 | freeze: false, 126 | name: globalName, 127 | interop: false, 128 | sourcemap: !isProduction, 129 | }); 130 | } catch (error) { 131 | console.log(chalk.bgRed.black(' BUILD FAIL '), tag); 132 | throw error; 133 | } 134 | console.log(chalk.bgGreen.black(' COMPLETE '), tag); 135 | } 136 | 137 | async function copyTo(from, to) { 138 | await mkdirp(path.dirname(to)); 139 | return new Promise((resolve, reject) => { 140 | ncp(from, to, err => { 141 | err && reject(err); 142 | resolve(); 143 | }); 144 | }); 145 | } 146 | 147 | /** 148 | * 将未参与打包的资源复制到输出目录中 149 | */ 150 | function copyResource() { 151 | const tasks = fs.readdirSync(path.join(cwd, 'packages')).map(async name => { 152 | const fromBaseDir = path.join(cwd, 'packages', name); 153 | const toBaseDir = path.join(cwd, 'build', name); 154 | if (!fs.existsSync(toBaseDir)) { 155 | // 直接复制整个目录到 build 156 | await mkdirp(toBaseDir); 157 | 158 | await copyTo(fromBaseDir, toBaseDir); 159 | return; 160 | } 161 | 162 | await copyTo(path.join(fromBaseDir, 'npm'), path.join(toBaseDir)); 163 | await copyTo('LICENSE', path.join(toBaseDir, 'LICENSE')); 164 | // await copyTo(path.join(fromBaseDir, 'package.json'), path.join(toBaseDir, 'package.json')); 165 | await copyTo('README.md', path.join(toBaseDir, 'README.md')); 166 | const pkg = require(path.join(fromBaseDir, 'package.json')); 167 | pkg.types = 'src/index.d.ts'; 168 | 169 | await fs.promises.writeFile(path.join(toBaseDir, 'package.json'), JSON.stringify(pkg), 'utf-8'); 170 | }); 171 | 172 | return Promise.all(tasks); 173 | } 174 | 175 | /** 176 | * 开始 Build 177 | * 打包 cjs + umd 模块 178 | */ 179 | async function build() { 180 | await rmfr('build'); 181 | 182 | for (let index = 0; index < bundles.packages.length; index++) { 183 | const package = bundles.packages[index]; 184 | await createBundle(package, bundles.CJS); 185 | await createBundle(package, bundles.CJS_DEV); 186 | } 187 | // 遍历所有的包 188 | await copyResource(); 189 | } 190 | 191 | build().catch(error => { 192 | console.error('build fail', error); 193 | }); 194 | -------------------------------------------------------------------------------- /examples/react-app-todolist/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 5 | 6 | /* Basic Options */ 7 | // "incremental": true, /* Enable incremental compilation */ 8 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 9 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 10 | // "lib": [], /* Specify library files to be included in the compilation. */ 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 14 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | // "outDir": "./", /* Redirect output structure to the directory. */ 19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true /* Enable all strict type-checking options. */, 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 55 | 56 | /* Source Map Options */ 57 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 59 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 60 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 61 | 62 | /* Experimental Options */ 63 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 64 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 65 | 66 | /* Advanced Options */ 67 | "skipLibCheck": true /* Skip type checking of declaration files. */, 68 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 69 | }, 70 | "include": ["./src/**/*", "./node_modules/@dxjs/**/*"] 71 | } 72 | -------------------------------------------------------------------------------- /packages/shared/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dxjs/shared", 3 | "version": "2.0.1-beta.13", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime": { 8 | "version": "7.9.2", 9 | "resolved": "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.9.2.tgz", 10 | "integrity": "sha1-2Q3wWDo6JS8JqqYZZlNnuuUY2wY=", 11 | "requires": { 12 | "regenerator-runtime": "^0.13.4" 13 | } 14 | }, 15 | "@redux-saga/core": { 16 | "version": "1.1.3", 17 | "resolved": "https://registry.npm.taobao.org/@redux-saga/core/download/@redux-saga/core-1.1.3.tgz", 18 | "integrity": "sha1-MIUJe1ek6o21Uo1YZz8gzglQ9qQ=", 19 | "requires": { 20 | "@babel/runtime": "^7.6.3", 21 | "@redux-saga/deferred": "^1.1.2", 22 | "@redux-saga/delay-p": "^1.1.2", 23 | "@redux-saga/is": "^1.1.2", 24 | "@redux-saga/symbols": "^1.1.2", 25 | "@redux-saga/types": "^1.1.0", 26 | "redux": "^4.0.4", 27 | "typescript-tuple": "^2.2.1" 28 | } 29 | }, 30 | "@redux-saga/deferred": { 31 | "version": "1.1.2", 32 | "resolved": "https://registry.npm.taobao.org/@redux-saga/deferred/download/@redux-saga/deferred-1.1.2.tgz", 33 | "integrity": "sha1-WZN6Drpx//KJ8TECM7xRgRenGIg=" 34 | }, 35 | "@redux-saga/delay-p": { 36 | "version": "1.1.2", 37 | "resolved": "https://registry.npm.taobao.org/@redux-saga/delay-p/download/@redux-saga/delay-p-1.1.2.tgz", 38 | "integrity": "sha1-j1FfSwCbBbAqN6fD0Mqd3BV7s1U=", 39 | "requires": { 40 | "@redux-saga/symbols": "^1.1.2" 41 | } 42 | }, 43 | "@redux-saga/is": { 44 | "version": "1.1.2", 45 | "resolved": "https://registry.npm.taobao.org/@redux-saga/is/download/@redux-saga/is-1.1.2.tgz", 46 | "integrity": "sha1-rmyEIfWPy6gPr3ytt9ZbMDuX5Y4=", 47 | "requires": { 48 | "@redux-saga/symbols": "^1.1.2", 49 | "@redux-saga/types": "^1.1.0" 50 | } 51 | }, 52 | "@redux-saga/symbols": { 53 | "version": "1.1.2", 54 | "resolved": "https://registry.npm.taobao.org/@redux-saga/symbols/download/@redux-saga/symbols-1.1.2.tgz", 55 | "integrity": "sha1-IWpnKkh/wlaHK4A0g1r8IqLQWV0=" 56 | }, 57 | "@redux-saga/types": { 58 | "version": "1.1.0", 59 | "resolved": "https://registry.npm.taobao.org/@redux-saga/types/download/@redux-saga/types-1.1.0.tgz", 60 | "integrity": "sha1-DoHOVrSIO0sqMAHr4aspi4QjcgQ=" 61 | }, 62 | "d": { 63 | "version": "1.0.1", 64 | "resolved": "https://registry.npm.taobao.org/d/download/d-1.0.1.tgz?cache=0&sync_timestamp=1560529642619&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fd%2Fdownload%2Fd-1.0.1.tgz", 65 | "integrity": "sha1-hpgJU3LVjb7jRv/Qxwk/mfj561o=", 66 | "requires": { 67 | "es5-ext": "^0.10.50", 68 | "type": "^1.0.1" 69 | } 70 | }, 71 | "es5-ext": { 72 | "version": "0.10.53", 73 | "resolved": "https://registry.npm.taobao.org/es5-ext/download/es5-ext-0.10.53.tgz", 74 | "integrity": "sha1-k8WjrP2+8nUiCtcmRK0C7hg2jeE=", 75 | "requires": { 76 | "es6-iterator": "~2.0.3", 77 | "es6-symbol": "~3.1.3", 78 | "next-tick": "~1.0.0" 79 | } 80 | }, 81 | "es6-iterator": { 82 | "version": "2.0.3", 83 | "resolved": "https://registry.npm.taobao.org/es6-iterator/download/es6-iterator-2.0.3.tgz", 84 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", 85 | "requires": { 86 | "d": "1", 87 | "es5-ext": "^0.10.35", 88 | "es6-symbol": "^3.1.1" 89 | } 90 | }, 91 | "es6-symbol": { 92 | "version": "3.1.3", 93 | "resolved": "https://registry.npm.taobao.org/es6-symbol/download/es6-symbol-3.1.3.tgz", 94 | "integrity": "sha1-utXTwbzawoJp9MszHkMceKxwXRg=", 95 | "requires": { 96 | "d": "^1.0.1", 97 | "ext": "^1.1.2" 98 | } 99 | }, 100 | "ext": { 101 | "version": "1.4.0", 102 | "resolved": "https://registry.npm.taobao.org/ext/download/ext-1.4.0.tgz", 103 | "integrity": "sha1-ia56BxWPedNVF4gpBDJAd+Q3kkQ=", 104 | "requires": { 105 | "type": "^2.0.0" 106 | }, 107 | "dependencies": { 108 | "type": { 109 | "version": "2.0.0", 110 | "resolved": "https://registry.npm.taobao.org/type/download/type-2.0.0.tgz", 111 | "integrity": "sha1-Xxb/bvLrRPJgSU2uJxAzspwJqcM=" 112 | } 113 | } 114 | }, 115 | "js-tokens": { 116 | "version": "4.0.0", 117 | "resolved": "https://registry.npm.taobao.org/js-tokens/download/js-tokens-4.0.0.tgz?cache=0&sync_timestamp=1586796260005&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjs-tokens%2Fdownload%2Fjs-tokens-4.0.0.tgz", 118 | "integrity": "sha1-GSA/tZmR35jjoocFDUZHzerzJJk=" 119 | }, 120 | "loose-envify": { 121 | "version": "1.4.0", 122 | "resolved": "https://registry.npm.taobao.org/loose-envify/download/loose-envify-1.4.0.tgz", 123 | "integrity": "sha1-ce5R+nvkyuwaY4OffmgtgTLTDK8=", 124 | "requires": { 125 | "js-tokens": "^3.0.0 || ^4.0.0" 126 | } 127 | }, 128 | "next-tick": { 129 | "version": "1.0.0", 130 | "resolved": "https://registry.npm.taobao.org/next-tick/download/next-tick-1.0.0.tgz", 131 | "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" 132 | }, 133 | "redux": { 134 | "version": "4.0.5", 135 | "resolved": "https://registry.npm.taobao.org/redux/download/redux-4.0.5.tgz", 136 | "integrity": "sha1-TbXeWBbheJHeioDEJCMtBvBR2T8=", 137 | "requires": { 138 | "loose-envify": "^1.4.0", 139 | "symbol-observable": "^1.2.0" 140 | } 141 | }, 142 | "redux-saga": { 143 | "version": "1.1.3", 144 | "resolved": "https://registry.npm.taobao.org/redux-saga/download/redux-saga-1.1.3.tgz", 145 | "integrity": "sha1-nz5q69PJlLvA9pAaYl+aQrUdERI=", 146 | "requires": { 147 | "@redux-saga/core": "^1.1.3" 148 | } 149 | }, 150 | "regenerator-runtime": { 151 | "version": "0.13.5", 152 | "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.5.tgz?cache=0&sync_timestamp=1584052481783&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.5.tgz", 153 | "integrity": "sha1-2Hih0JS0MG0QuQlkhLM+vVXiZpc=" 154 | }, 155 | "symbol-observable": { 156 | "version": "1.2.0", 157 | "resolved": "https://registry.npm.taobao.org/symbol-observable/download/symbol-observable-1.2.0.tgz", 158 | "integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=" 159 | }, 160 | "type": { 161 | "version": "1.2.0", 162 | "resolved": "https://registry.npm.taobao.org/type/download/type-1.2.0.tgz", 163 | "integrity": "sha1-hI3XaY2vo+VKbEeedZxLw/GIR6A=" 164 | }, 165 | "typescript-compare": { 166 | "version": "0.0.2", 167 | "resolved": "https://registry.npm.taobao.org/typescript-compare/download/typescript-compare-0.0.2.tgz", 168 | "integrity": "sha1-fuQKQApAbC6gp+VR79MwkCHV9CU=", 169 | "requires": { 170 | "typescript-logic": "^0.0.0" 171 | } 172 | }, 173 | "typescript-logic": { 174 | "version": "0.0.0", 175 | "resolved": "https://registry.npm.taobao.org/typescript-logic/download/typescript-logic-0.0.0.tgz", 176 | "integrity": "sha1-ZuvYKiVI8rREpDZnvsEgtJaJAZY=" 177 | }, 178 | "typescript-tuple": { 179 | "version": "2.2.1", 180 | "resolved": "https://registry.npm.taobao.org/typescript-tuple/download/typescript-tuple-2.2.1.tgz", 181 | "integrity": "sha1-fZgT+0s1X2msVQMuA2Pouw8E2tI=", 182 | "requires": { 183 | "typescript-compare": "^0.0.2" 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /scripts/jest/jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | // For a detailed explanation regarding each configuration property, visit: 3 | // https://jestjs.io/docs/en/configuration.html 4 | 5 | module.exports = { 6 | // All imported modules in your tests should be mocked automatically 7 | // automock: false, 8 | 9 | // Stop running tests after `n` failures 10 | // bail: 0, 11 | 12 | // Respect "browser" field in package.json when resolving modules 13 | // browser: false, 14 | 15 | // The directory where Jest should store its cached dependency information 16 | // cacheDirectory: "/private/var/folders/8g/p9t__m2143717fkwvdj348s40000gn/T/jest_dx", 17 | 18 | // Automatically clear mock calls and instances between every test 19 | clearMocks: true, 20 | 21 | // Indicates whether the coverage information should be collected while executing the test 22 | collectCoverage: false, 23 | 24 | // An array of glob patterns indicating a set of files for which coverage information should be collected 25 | // collectCoverageFrom: [], 26 | 27 | // The directory where Jest should output its coverage files 28 | coverageDirectory: 'coverage', 29 | 30 | // An array of regexp pattern strings used to skip coverage collection 31 | // coveragePathIgnorePatterns: [ 32 | // "/node_modules/" 33 | // ], 34 | 35 | // A list of reporter names that Jest uses when writing coverage reports 36 | // coverageReporters: [ 37 | // "json", 38 | // "text", 39 | // "lcov", 40 | // "clover" 41 | // ], 42 | 43 | // An object that configures minimum threshold enforcement for coverage results 44 | // coverageThreshold: undefined, 45 | 46 | // A path to a custom dependency extractor 47 | // dependencyExtractor: undefined, 48 | 49 | // Make calling deprecated APIs throw helpful error messages 50 | // errorOnDeprecated: false, 51 | 52 | // Force coverage collection from ignored files using an array of glob patterns 53 | // forceCoverageMatch: [], 54 | 55 | // A path to a module which exports an async function that is triggered once before all test suites 56 | // globalSetup: undefined, 57 | 58 | // A path to a module which exports an async function that is triggered once after all test suites 59 | // globalTeardown: undefined, 60 | 61 | // A set of global variables that need to be available in all test environments 62 | globals: { 63 | __DEV__: false, 64 | }, 65 | 66 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 67 | // maxWorkers: "50%", 68 | 69 | // An array of directory names to be searched recursively up from the requiring module's location 70 | // moduleDirectories: [ 71 | // "node_modules" 72 | // ], 73 | 74 | // An array of file extensions your modules use 75 | // moduleFileExtensions: [ 76 | // "js", 77 | // "json", 78 | // "jsx", 79 | // "ts", 80 | // "tsx", 81 | // "node" 82 | // ], 83 | 84 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 85 | // moduleNameMapper: {}, 86 | 87 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 88 | // modulePathIgnorePatterns: [], 89 | 90 | // Activates notifications for test results 91 | // notify: false, 92 | 93 | // An enum that specifies notification mode. Requires { notify: true } 94 | // notifyMode: "failure-change", 95 | 96 | // A preset that is used as a base for Jest's configuration 97 | // preset: undefined, 98 | 99 | // Run tests from one or more projects 100 | // projects: undefined, 101 | 102 | // Use this configuration option to add custom reporters to Jest 103 | // reporters: undefined, 104 | 105 | // Automatically reset mock state between every test 106 | // resetMocks: false, 107 | 108 | // Reset the module registry before running each individual test 109 | // resetModules: false, 110 | 111 | // A path to a custom resolver 112 | // resolver: undefined, 113 | 114 | // Automatically restore mock state between every test 115 | // restoreMocks: false, 116 | 117 | // The root directory that Jest should scan for tests and modules within 118 | rootDir: path.join(process.cwd(), 'packages'), 119 | 120 | // A list of paths to directories that Jest should use to search for files in 121 | // roots: [ 122 | // "" 123 | // ], 124 | 125 | // Allows you to use a custom runner instead of Jest's default test runner 126 | // runner: "jest-runner", 127 | 128 | // The paths to modules that run some code to configure or set up the testing environment before each test 129 | setupFiles: [path.join(__dirname, "./jest.setup.js")], 130 | 131 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 132 | // setupFilesAfterEnv: [], 133 | 134 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 135 | // snapshotSerializers: [], 136 | 137 | // The test environment that will be used for testing 138 | testEnvironment: 'node', 139 | 140 | // Options that will be passed to the testEnvironment 141 | // testEnvironmentOptions: {}, 142 | 143 | // Adds a location field to test results 144 | // testLocationInResults: false, 145 | 146 | // The glob patterns Jest uses to detect test files 147 | testMatch: [ 148 | // "**/__tests__/**/*.[jt]s?(x)", 149 | '**/?(*.)+(spec|test).[tj]s?(x)', 150 | ], 151 | 152 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 153 | // testPathIgnorePatterns: [ 154 | // "/node_modules/" 155 | // ], 156 | 157 | // The regexp pattern or array of patterns that Jest uses to detect test files 158 | // testRegex: [], 159 | 160 | // This option allows the use of a custom results processor 161 | // testResultsProcessor: undefined, 162 | 163 | // This option allows use of a custom test runner 164 | // testRunner: "jasmine2", 165 | 166 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 167 | // testURL: "http://localhost", 168 | 169 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 170 | // timers: "real", 171 | 172 | // A map from regular expressions to paths to transformers 173 | transform: { 174 | '.(ts|tsx)': ['babel-jest', { configFile: path.join(process.cwd(), '.babelrc') }], 175 | }, 176 | 177 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 178 | // transformIgnorePatterns: [ 179 | // "/node_modules/" 180 | // ], 181 | 182 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 183 | // unmockedModulePathPatterns: undefined, 184 | 185 | // Indicates whether each individual test should be reported during the run 186 | // verbose: undefined, 187 | 188 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 189 | // watchPathIgnorePatterns: [], 190 | 191 | // Whether to use watchman for file crawling 192 | // watchman: true, 193 | }; 194 | -------------------------------------------------------------------------------- /packages/core/src/dx-model/base.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { PuttableChannel, END, Task } from 'redux-saga'; 3 | import { Action } from 'redux'; 4 | import { 5 | PutEffect, 6 | ChannelPutEffect, 7 | CallEffect, 8 | SagaReturnType, 9 | CpsFunctionParameters, 10 | CpsEffect, 11 | ForkEffect, 12 | JoinEffect, 13 | CancelEffect, 14 | SelectEffect, 15 | Tail, 16 | AllEffect, 17 | RaceEffect, 18 | put, 19 | putResolve, 20 | call, 21 | apply, 22 | cps, 23 | fork, 24 | join, 25 | cancel, 26 | select, 27 | delay, 28 | all, 29 | race, 30 | CpsCallback, 31 | spawn, 32 | retry, 33 | } from 'redux-saga/effects'; 34 | 35 | export class DxBase { 36 | $put(channel: PuttableChannel, action: T | END): ChannelPutEffect; 37 | $put(action: A): PutEffect; 38 | $put(resolve: 'resolve', action: A): PutEffect; 39 | $put( 40 | resolve: 'resolve' | A | PuttableChannel, 41 | action?: A | END, 42 | ): PutEffect | ChannelPutEffect { 43 | if (resolve === 'resolve') { 44 | return putResolve(action as A); 45 | } 46 | 47 | if ('type' in resolve) { 48 | return put(resolve); 49 | } 50 | 51 | return put(resolve, action); 52 | } 53 | 54 | $call any>(fn: Fn, ...args: Parameters): CallEffect>; 55 | $call any>( 56 | ctxAndFn: [Ctx, Fn], 57 | ...args: Parameters 58 | ): CallEffect>; 59 | $call any>( 60 | ctxAndFn: { context: Ctx; fn: Fn }, 61 | ...args: Parameters 62 | ): CallEffect>; 63 | $call any>(fn: Fn, ...args: Parameters): CallEffect> { 64 | return call(fn, ...args); 65 | } 66 | 67 | $apply any }, Name extends string>( 68 | ctx: Ctx, 69 | fnName: Name, 70 | args: Parameters, 71 | ): CallEffect>; 72 | $apply any>( 73 | ctx: Ctx, 74 | fn: Fn, 75 | args: Parameters, 76 | ): CallEffect>; 77 | $apply any>( 78 | ctx: Ctx, 79 | fn: Fn, 80 | args: Parameters, 81 | ): CallEffect> { 82 | return apply(ctx, fn, args); 83 | } 84 | 85 | $cps) => any>(fn: Fn): CpsEffect>; 86 | $cps void }, Name extends string>( 87 | ctxAndFnName: [Ctx, Name], 88 | ...args: CpsFunctionParameters 89 | ): CpsEffect>; 90 | $cps void }, Name extends string>( 91 | ctxAndFnName: { context: Ctx; fn: Name }, 92 | ...args: CpsFunctionParameters 93 | ): CpsEffect>; 94 | $cps void>( 95 | ctxAndFn: [Ctx, Fn], 96 | ...args: CpsFunctionParameters 97 | ): CpsEffect>; 98 | $cps void>( 99 | ctxAndFn: { context: Ctx; fn: Fn }, 100 | ...args: CpsFunctionParameters 101 | ): CpsEffect>; 102 | $cps any>(fn: Fn, ...args: CpsFunctionParameters): CpsEffect>; 103 | $cps any>(fn: Fn, ...args: CpsFunctionParameters): CpsEffect> { 104 | return cps(fn, ...args); 105 | } 106 | 107 | $fork any }, Name extends string>( 108 | ctxAndFnName: [Ctx, Name], 109 | ...args: Parameters 110 | ): ForkEffect>; 111 | $fork any }, Name extends string>( 112 | ctxAndFnName: { context: Ctx; fn: Name }, 113 | ...args: Parameters 114 | ): ForkEffect>; 115 | $fork any>( 116 | ctxAndFn: [Ctx, Fn], 117 | ...args: Parameters 118 | ): ForkEffect>; 119 | $fork any>( 120 | ctxAndFn: { context: Ctx; fn: Fn }, 121 | ...args: Parameters 122 | ): ForkEffect>; 123 | $fork any>(fn: Fn, ...args: Parameters): ForkEffect>; 124 | $fork any>(fn: Fn, ...args: Parameters): ForkEffect> { 125 | return fork(fn, ...args); 126 | } 127 | 128 | $spawn any }, Name extends string>( 129 | ctxAndFnName: [Ctx, Name], 130 | ...args: Parameters 131 | ): ForkEffect>; 132 | $spawn any }, Name extends string>( 133 | ctxAndFnName: { context: Ctx; fn: Name }, 134 | ...args: Parameters 135 | ): ForkEffect>; 136 | $spawn any>( 137 | ctxAndFn: [Ctx, Fn], 138 | ...args: Parameters 139 | ): ForkEffect>; 140 | $spawn any>( 141 | ctxAndFn: { context: Ctx; fn: Fn }, 142 | ...args: Parameters 143 | ): ForkEffect>; 144 | $spawn any>(fn: Fn, ...args: Parameters): ForkEffect>; 145 | $spawn any>(fn: Fn, ...args: Parameters): ForkEffect> { 146 | return spawn(fn, ...args); 147 | } 148 | 149 | $join(task: Task | Task[]): JoinEffect { 150 | // 满足 effect types 151 | if (Array.isArray(task)) return join(task); 152 | return join(task); 153 | } 154 | 155 | $cancel(task: Task | Task[]): CancelEffect { 156 | if (Array.isArray(task)) return cancel(task); 157 | return cancel(task); 158 | } 159 | 160 | $select(): SelectEffect; 161 | $select any>(selector: Fn, ...args: Tail>): SelectEffect; 162 | $select any>(selector?: Fn, ...args: Tail>): SelectEffect { 163 | if (typeof selector === 'undefined') return select(); 164 | return select(selector, ...args); 165 | } 166 | 167 | $delay(ms: number, val?: T): CallEffect { 168 | return delay(ms, val); 169 | } 170 | 171 | $retry any>( 172 | maxTries: number, 173 | delayLength: number, 174 | fn: Fn, 175 | ...args: Parameters 176 | ): CallEffect> { 177 | return retry(maxTries, delayLength, fn, ...args); 178 | } 179 | 180 | $all(effects: T[]): AllEffect; 181 | $all(effects: { [key: string]: T }): AllEffect; 182 | $all(effects: { [key: string]: T }): AllEffect { 183 | return all(effects); 184 | } 185 | 186 | $race(effects: { [key: string]: T }): RaceEffect; 187 | $race(effects: T[]): RaceEffect; 188 | $race(effects: T[]): RaceEffect { 189 | return race(effects); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.3.1](https://github.com/taixw2/dx/compare/v1.3.2...v1.3.1) (2020-05-12) 2 | 3 | 4 | 5 | # [1.2.0](https://github.com/taixw2/dx/compare/v1.0.2...v1.2.0) (2020-04-30) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * miss npm token env ([fe5f312](https://github.com/taixw2/dx/commit/fe5f3127683c0884af86a079b90da7b44676f4c6)) 11 | * no publish with current version ([00c3f0f](https://github.com/taixw2/dx/commit/00c3f0f19b16dfd309d30d22cde8d5840a7ba9dc)) 12 | * publish "The following paths are ignored" ([5481db7](https://github.com/taixw2/dx/commit/5481db7e0c8895d9f1f9ed99ed06ce22b7337cad)) 13 | * publish auto increment ([26837db](https://github.com/taixw2/dx/commit/26837db4d529a2a98bb7be935b751247f8a083d0)) 14 | * 一些编译时的 bug ([124a68b](https://github.com/taixw2/dx/commit/124a68bc399c1dda421a23671a5d55ab205028a6)) 15 | 16 | 17 | ### Features 18 | 19 | * 获取方法中的增强器 ([cfbc5e2](https://github.com/taixw2/dx/commit/cfbc5e2cd214020be52fc3856681818206438f82)) 20 | * **@dxjs/core:** effect 超类 + ts声明 ([0841a71](https://github.com/taixw2/dx/commit/0841a715503fd8cacc23ebcf3211ec8514cc1540)) 21 | * **@dxjs/core:** 为测试环境返回 inst ([d888bcf](https://github.com/taixw2/dx/commit/d888bcf77b02a5578b93d73bc0dc0a4b2081e3c7)) 22 | * **@dxjs/core:** 拆分 combin saga ([d8419a4](https://github.com/taixw2/dx/commit/d8419a4dec75d985692e26c8bc9198e0faddd146)) 23 | * **@dxjs/core:** 拆分 create-reducer 目录 ([86f04b3](https://github.com/taixw2/dx/commit/86f04b3e334dbd91afe984252df9f6d574fcd9e2)) 24 | * **@dxjs/core:** 拆分 saga 目录 ([8414bb3](https://github.com/taixw2/dx/commit/8414bb37cb78a5b5890374239e55eebcdaa44d85)) 25 | * **@dxjs/core:** 新增 effect 哨兵,伪装者,守卫 功能 ([bf37cba](https://github.com/taixw2/dx/commit/bf37cbad57fa65334173f41075c12322d2d6825d)) 26 | * **@dxjs/core:** 新增 isDxModel 工具函数 ([270bf0c](https://github.com/taixw2/dx/commit/270bf0cabea3e229ffc0e0aed0307e71d4677861)) 27 | * **@dxjs/core:** 新增 promise & 部分逻辑重构 ([d953aa1](https://github.com/taixw2/dx/commit/d953aa196779bc74e899b41eda7ab85994aa66fc)) 28 | * **@dxjs/core:** 新增 snetinel 功能 ([255fc71](https://github.com/taixw2/dx/commit/255fc713416ebfd9b17581eafd3904ffc855e13f)) 29 | * **@dxjs/core:** 新增两个 helper ([efb2c33](https://github.com/taixw2/dx/commit/efb2c332b5cfc0ff9fba9bf14bae005e72abe75a)) 30 | * **@dxjs/core:** 记录所有的 action type ([63140fc](https://github.com/taixw2/dx/commit/63140fc6dba04610ec929b6e83e92ab533429c88)) 31 | * **@dxjs/shared:** 新增 哨兵,伪装者,守卫 声明类型 ([f1e86bf](https://github.com/taixw2/dx/commit/f1e86bf5c27348509bf1d2ed78096a56f10b5060)) 32 | * create release action ([5d7a82b](https://github.com/taixw2/dx/commit/5d7a82b53b57d02b119ddc5c690672d06cffae44)) 33 | * create release script and refactor build ([2488c34](https://github.com/taixw2/dx/commit/2488c34d8a487d7ab47855424254c0252c2203d2)) 34 | * 完善 readme ([9011d93](https://github.com/taixw2/dx/commit/9011d936d9f834fbbdc94872b4e337d101b7ceb0)) 35 | * **example:** 新增 taro example ([016b060](https://github.com/taixw2/dx/commit/016b06029070d5514dfe5c49c2b9729ebb3db0bf)) 36 | 37 | 38 | 39 | # [1.1.0](https://github.com/taixw2/dx/compare/v1.0.2...v1.1.0) (2020-04-24) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * 一些编译时的 bug ([8d208ac](https://github.com/taixw2/dx/commit/8d208ac0dfc110940ce7e9ebd32f411d617f993f)) 45 | 46 | 47 | ### Features 48 | 49 | * **@dxjs/core:** 为测试环境返回 inst ([6d89047](https://github.com/taixw2/dx/commit/6d890477ef6ffbfb615d2f9e240c6bcd43577493)) 50 | * **@dxjs/core:** 拆分 create-reducer 目录 ([9b3d910](https://github.com/taixw2/dx/commit/9b3d9101a041e4ffe99abe50eb65405475a09bee)) 51 | * **@dxjs/core:** 拆分 saga 目录 ([a27fda7](https://github.com/taixw2/dx/commit/a27fda7f5fbe1b3c7f0329a1074417e9132bf7fe)) 52 | * **@dxjs/core:** 新增 isDxModel 工具函数 ([61682f1](https://github.com/taixw2/dx/commit/61682f1f3a9114902970d7d82c9e19d13f9755ca)) 53 | * **@dxjs/core:** 新增 promise & 部分逻辑重构 ([b125393](https://github.com/taixw2/dx/commit/b1253930312cf46fc3c60556d1c6b4e0e02c8bef)) 54 | * **@dxjs/core:** 新增两个 helper ([6c0eb1d](https://github.com/taixw2/dx/commit/6c0eb1df06e4856e26db3abc0a2247b8d208624a)) 55 | * **@dxjs/core:** 记录所有的 action type ([248793b](https://github.com/taixw2/dx/commit/248793b6ec8da533b7b212e3d383105bcd74442b)) 56 | * **example:** 新增 taro example ([016b060](https://github.com/taixw2/dx/commit/016b06029070d5514dfe5c49c2b9729ebb3db0bf)) 57 | 58 | 59 | 60 | ## 1.0.2 (2020-04-21) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * build fail ([2cfaeae](https://github.com/taixw2/dx/commit/2cfaeae07ded1655fd4bca148fe96de515e70998)) 66 | * **@dxjs/common:** metadata 从 constructior 获取 ([4e1e1f1](https://github.com/taixw2/dx/commit/4e1e1f18d73f045f456a10b1cf3f490c7f89a5f1)) 67 | * **@dxjs/common:** getMetadata 参数错误 ([baaea6a](https://github.com/taixw2/dx/commit/baaea6af13aee3853224b50fa15828ecbb028d37)) 68 | * **@dxjs/common:** 导出可读的 helper ([62a9392](https://github.com/taixw2/dx/commit/62a93921dd713289b5d89b5efb3b2b1a6f37b581)) 69 | * **@dxjs/common:** 缺少 polyfill ([fa23c18](https://github.com/taixw2/dx/commit/fa23c180681b0c6917f2c0f8cd0a21a23e8b0cb8)) 70 | * **@dxjs/core:** collect development 通过 proto 判断 ([5483d96](https://github.com/taixw2/dx/commit/5483d9606cd1e344590748710f784c145de13fa7)) 71 | * **@dxjs/core:** craete reducer 使用最新的 state ([4d65b1b](https://github.com/taixw2/dx/commit/4d65b1b4666c64c7350b22eece63fa36af88354b)) 72 | * **@dxjs/core:** create reducer 中 enhancer 为空 ([fc10e63](https://github.com/taixw2/dx/commit/fc10e63a285bc56d9b99b7f0f665c492b8b90281)) 73 | * **@dxjs/core:** fix 类型不匹配 ([264f46b](https://github.com/taixw2/dx/commit/264f46be7b7ce9ee1fac3a1ebea76ea55580cc4d)) 74 | * **@dxjs/core:** reducers & effects 为空情况的判断 ([984dd38](https://github.com/taixw2/dx/commit/984dd380069894b88eda92963acd1525f69f7083)) 75 | * **@dxjs/core:** type 获取错误 ([23fe505](https://github.com/taixw2/dx/commit/23fe505070bae42b594dbbc01a42a3a47b302b47)) 76 | * **@dxjs/core:** 一些异常情况 ([f2b7ba4](https://github.com/taixw2/dx/commit/f2b7ba4ecb1b92b7f3925c5f1212bd8eae6ff7b9)) 77 | * **@dxjs/core:** 直接传入 reducer 和 effect payload ([deb6ca5](https://github.com/taixw2/dx/commit/deb6ca58f2c77c70c084387c30cf6530a820af2f)) 78 | * **@dxjs/core:** 获取 model 自定义名称 ([7b6e777](https://github.com/taixw2/dx/commit/7b6e777472f790edbe010692cfc52c44880f82ee)) 79 | * **@dxjs/core&@dxjs/shared:** 一些基础错误 ([e47b678](https://github.com/taixw2/dx/commit/e47b6785243b3de2eea1d9d9cc6ac04d4d6b38a6)) 80 | * **core:** reducerEnhancer initial error ([4184322](https://github.com/taixw2/dx/commit/4184322bbc9d969858fed60bac4106bae262065b)) 81 | 82 | 83 | ### Features 84 | 85 | * **@dxjs/core:** hack taro ([e29ccf1](https://github.com/taixw2/dx/commit/e29ccf1eb7662af8747924a87123869e91a81c37)) 86 | * workflows ([6369f52](https://github.com/taixw2/dx/commit/6369f52b1ca07bbcae199de6ba66a24c5e22ebe2)) 87 | * 新增各 package 入口文件 ([fa5ad2a](https://github.com/taixw2/dx/commit/fa5ad2a3fa16d2c9e5c83ba37d7f07f111746024)) 88 | * 集成 github action ([6da346c](https://github.com/taixw2/dx/commit/6da346cf923d9a69517d51d4e118163e6233ee16)) 89 | * **@dxjs/common:** first commit ([2fb971e](https://github.com/taixw2/dx/commit/2fb971e4bc8230c4a460c820aca7606669e3a504)) 90 | * **@dxjs/core:** create return type ([3f3d927](https://github.com/taixw2/dx/commit/3f3d927f5394d98052c92eb17c1100fdfbac8827)) 91 | * **@dxjs/core:** Dx.create 拆分 ([b60d3c3](https://github.com/taixw2/dx/commit/b60d3c318e1bcad2b39cbfc9a0ae32bc7a5087c0)) 92 | * **@dxjs/core:** first commit ([a1f73b6](https://github.com/taixw2/dx/commit/a1f73b68f11a943155c133e90bd7f02edd9d16bc)) 93 | * **@dxjs/core:** 在开发环境判断收集器手机的是否为空 ([5942605](https://github.com/taixw2/dx/commit/59426056467763b6c0b1b12d4185315c1a082878)) 94 | * **@dxjs/core:** 基础的 example ([d23a497](https://github.com/taixw2/dx/commit/d23a497377b1b0ae3cdc965ac9da48a9b3d0f67d)) 95 | * **@dxjs/core:** 缓存 store, 避免重复走创建流程 ([40d1d33](https://github.com/taixw2/dx/commit/40d1d33f66183a1adafc07d44e9cbc6565da4a45)) 96 | * **@dxjs/shared:** access public ([8201190](https://github.com/taixw2/dx/commit/820119030b2f514c4be48aa5350d08b8cdad706e)) 97 | * **example:** create-react-app first commit ([7735c7f](https://github.com/taixw2/dx/commit/7735c7f5271ca06268b668ebc5881ed243f59d65)) 98 | * **example:** create-react-app first commit ([0402a9a](https://github.com/taixw2/dx/commit/0402a9ac06f1b82f609975cae2cbd61e25df8d85)) 99 | * create readme ([4d4bed5](https://github.com/taixw2/dx/commit/4d4bed55c7a71cb12b08fbc33491eff974d0b6cf)) 100 | * first-commit ([60bc246](https://github.com/taixw2/dx/commit/60bc2469d37124876e708b6d5326c439c7839eec)) 101 | * 基础规划 ([56eb623](https://github.com/taixw2/dx/commit/56eb62303d22cdcc33469b40147a8306a79d8059)) 102 | 103 | 104 | 105 | --------------------------------------------------------------------------------