├── lib ├── types │ ├── index.js │ └── index.d.ts ├── index.d.ts └── index.js ├── jest.config.js ├── .babelrc ├── .coveralls.yml ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── .prettierrc ├── .fatherrc.ts ├── tsconfig.json ├── docs ├── static │ ├── css │ │ ├── example-example.8c852691c15934bea2f5.css │ │ └── example-index.8c852691c15934bea2f5.css │ └── js │ │ ├── example-index.2458096f.js │ │ ├── example-example.35eb3e9d.js │ │ └── app.8c852691c15934bea2f5.js ├── assets.json └── index.html ├── example ├── index.css ├── store.ts ├── index.tsx ├── Example.tsx ├── index.mdx └── Example.mdx ├── package.json ├── test ├── typescript │ └── index.tsx └── index.spec.tsx ├── src ├── types │ └── index.ts └── index.tsx └── README.md /lib/types/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | collectCoverage: true, 4 | }; -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-typescript", "@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | - export COVERALLS_SERVICE_NAME=travis-ci 2 | - export COVERALLS_REPO_TOKEN=hv5Yj6YRQIpprdNsRBly7yhNRMqXrCfef -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=TypeScript 2 | *.css linguist-language=TypeScript 3 | *.html linguist-language=TypeScript -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lerna-debug.log 3 | Pampasfile.js 4 | .vscode 5 | mock 6 | public 7 | .cache-loader 8 | .eslintcache 9 | .DS_Store 10 | coverage 11 | .docz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | public 4 | .DS_Store 5 | coverage 6 | .docz 7 | example 8 | docs 9 | .travis.yml 10 | jest.config.js 11 | tsconfig.json 12 | src 13 | test 14 | .babelrc 15 | .fatherrc.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 10 5 | branches: 6 | only: 7 | - master 8 | script: 9 | - npm run test 10 | - npm run build 11 | after_success: 12 | - npm run coveralls -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": false, 4 | "jsxBracketSameLine": false, 5 | "printWidth": 80, 6 | "proseWrap": "always", 7 | "semi": false, 8 | "singleQuote": true, 9 | "tabWidth": 2, 10 | "trailingComma": "all", 11 | "useTabs": false 12 | } 13 | -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | entry: 'src/index.jsx', 3 | doc: { 4 | title: 'react-vuex-hook使用文档', 5 | base: process.env.NODE_ENV === 'production' ? '/react-vuex-hook/' : '', 6 | dest: 'docs', 7 | typescript: true 8 | }, 9 | cjs: { 10 | type:'babel', 11 | minify: true 12 | }, 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "sourceMap": true, 10 | "baseUrl": ".", 11 | "allowSyntheticDefaultImports": true, 12 | "declaration": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IState, IOptions, IConnect, IContext } from './types'; 2 | export default function initStore(options?: IOptions): { 3 | connect: IConnect; 4 | useStore: () => IContext, MutationsKey, GettersKey, ActionsKey>; 5 | }; 6 | -------------------------------------------------------------------------------- /docs/static/css/example-example.8c852691c15934bea2f5.css: -------------------------------------------------------------------------------- 1 | .flex,.left{display:flex}.left{flex-direction:column;height:70vh;flex-basis:50%;margin-right:12px}.left .count{flex-basis:34vh;margin-bottom:2vh}.left .count .ant-card,.left .count .wrap{height:100%;margin-bottom:12px}.left .chat{flex-basis:34vh}.left .chat .ant-card,.left .chat .wrap{height:100%}.right{flex:1 1;height:70vh}.right .ant-card,.right .wrap{height:100%}.right .ant-card{overflow-y:auto}.card{margin-bottom:36px}.log{margin-bottom:12px} -------------------------------------------------------------------------------- /docs/static/css/example-index.8c852691c15934bea2f5.css: -------------------------------------------------------------------------------- 1 | .flex,.left{display:flex}.left{flex-direction:column;height:70vh;flex-basis:50%;margin-right:12px}.left .count{flex-basis:34vh;margin-bottom:2vh}.left .count .ant-card,.left .count .wrap{height:100%;margin-bottom:12px}.left .chat{flex-basis:34vh}.left .chat .ant-card,.left .chat .wrap{height:100%}.right{flex:1 1;height:70vh}.right .ant-card,.right .wrap{height:100%}.right .ant-card{overflow-y:auto}.card{margin-bottom:36px}.log{margin-bottom:12px} -------------------------------------------------------------------------------- /docs/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "vendors.css": "/react-vuex-hook/static/css/vendors.8c852691c15934bea2f5.css", 3 | "vendors.js": "/react-vuex-hook/static/js/vendors.3dba3285.js", 4 | "app.js": "/react-vuex-hook/static/js/app.8c852691c15934bea2f5.js", 5 | "example-example.css": "/react-vuex-hook/static/css/example-example.8c852691c15934bea2f5.css", 6 | "example-example.js": "/react-vuex-hook/static/js/example-example.35eb3e9d.js", 7 | "example-index.css": "/react-vuex-hook/static/css/example-index.8c852691c15934bea2f5.css", 8 | "example-index.js": "/react-vuex-hook/static/js/example-index.2458096f.js", 9 | "index.html": "/react-vuex-hook/index.html" 10 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | react-vuex-hook使用文档
-------------------------------------------------------------------------------- /example/index.css: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | } 4 | 5 | .left { 6 | display: flex; 7 | flex-direction: column; 8 | height: 70vh; 9 | flex-basis: 50%; 10 | margin-right: 12px; 11 | } 12 | 13 | .left .count { 14 | flex-basis: 34vh; 15 | margin-bottom: 2vh 16 | } 17 | 18 | .left .count .wrap, 19 | .left .count .ant-card { 20 | height: 100%; 21 | margin-bottom: 12px; 22 | } 23 | 24 | .left .chat { 25 | flex-basis: 34vh; 26 | } 27 | 28 | .left .chat .wrap, 29 | .left .chat .ant-card { 30 | height: 100%; 31 | } 32 | 33 | .right { 34 | flex: 1; 35 | height: 70vh; 36 | } 37 | 38 | .right .wrap, 39 | .right .ant-card { 40 | height: 100%; 41 | } 42 | 43 | .right .ant-card { 44 | overflow-y: auto; 45 | } 46 | 47 | .card { 48 | width: 300px; 49 | margin-bottom: 36px; 50 | } 51 | 52 | .chunk { 53 | margin-bottom: 12px; 54 | } 55 | 56 | .btn { 57 | margin-right: 8px; 58 | } 59 | 60 | .log { 61 | margin-bottom: 12px; 62 | } 63 | -------------------------------------------------------------------------------- /example/store.ts: -------------------------------------------------------------------------------- 1 | // store.js 2 | import initStore from '../src'; 3 | 4 | const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time)); 5 | 6 | export const { connect, useStore } = initStore({ 7 | // 初始状态 8 | getInitState: () => ({ 9 | count: 0, 10 | message: 'Hello', 11 | logs: [] as string[], 12 | }), 13 | // 同步操作 必须返回state的拷贝值 14 | mutations: { 15 | // 浅拷贝state 16 | add(state) { 17 | return Object.assign({}, state, { count: state.count + 1 }); 18 | }, 19 | chat(state, message: string) { 20 | return { 21 | ...state, 22 | message, 23 | }; 24 | }, 25 | log(state, payload: string) { 26 | return { 27 | ...state, 28 | logs: [payload, ...state.logs], 29 | }; 30 | }, 31 | }, 32 | // 异步操作,拥有dispatch的执行权 33 | actions: { 34 | async asyncAdd({ dispatch, state, getters }, payload) { 35 | await wait(1000); 36 | dispatch({ type: 'add' }); 37 | // 返回的值会被包裹的promise resolve 38 | return true; 39 | }, 40 | }, 41 | // 计算属性 根据state里的值动态计算 42 | // 在页面中根据state值的变化而动态变化 43 | getters: { 44 | countPlusOne(state) { 45 | return state.count + 1; 46 | }, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-vuex-hook", 3 | "version": "5.0.3", 4 | "description": "", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "keywords": [ 8 | "react", 9 | "reducer", 10 | "state", 11 | "store", 12 | "useReducer", 13 | "hook", 14 | "react-vuex", 15 | "vuex", 16 | "typescript" 17 | ], 18 | "scripts": { 19 | "test": "jest", 20 | "build": "father build & father doc build", 21 | "doc:dev": "father doc dev", 22 | "doc:build": "father doc build", 23 | "doc:deploy": "father doc deploy", 24 | "coveralls": "cat ./coverage/lcov.info | coveralls", 25 | "release": "npm version patch && npm run test && npm run build && npm publish" 26 | }, 27 | "dependencies": { 28 | "hoist-non-react-statics": "^3.3.0", 29 | "prop-types": "^15.7.2" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "^7.6.4", 33 | "@babel/preset-env": "^7.6.3", 34 | "@babel/preset-react": "^7.6.3", 35 | "@babel/preset-typescript": "^7.7.2", 36 | "@testing-library/react": "^9.3.1", 37 | "@testing-library/react-hooks": "^3.1.1", 38 | "@types/antd": "^1.0.0", 39 | "@types/hoist-non-react-statics": "^3.3.1", 40 | "@types/jest": "^24.0.21", 41 | "antd": "^3.25.0", 42 | "coveralls": "^3.0.7", 43 | "father": "^2.24.1", 44 | "jest": "^24.9.0", 45 | "react": "^16.11.0", 46 | "react-dom": "^16.11.0", 47 | "react-test-renderer": "^16.11.0" 48 | }, 49 | "peerDependencies": { 50 | "react": ">= 16.8.0", 51 | "react-dom": ">= 16.8.0" 52 | }, 53 | "author": "", 54 | "license": "ISC" 55 | } 56 | -------------------------------------------------------------------------------- /test/typescript/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import ReactDOM from "react-dom" 3 | import initStore from "../../src" 4 | 5 | const { connect, useStore } = initStore({ 6 | getInitState: () => ({ 7 | count: 1, 8 | logs: [] as string[], 9 | }), 10 | mutations: { 11 | // 浅拷贝state 12 | add(state) { 13 | return Object.assign({}, state, { count: state.count + 1 }) 14 | }, 15 | log(state, log: string) { 16 | return Object.assign({}, state, { logs: [...state.logs, log] }) 17 | }, 18 | }, 19 | getters: { 20 | countPlusOne(state) { 21 | return state.count + 1 22 | }, 23 | logStr(state) { 24 | return state.logs.join(",") 25 | }, 26 | }, 27 | actions: { 28 | async asyncAdd({ dispatch, state, getters }) { 29 | state.count + 1 30 | getters.countPlusOne + 1 31 | dispatch({ type: "add" }) 32 | // 返回的值会被包裹的promise resolve 33 | return true 34 | }, 35 | }, 36 | }) 37 | 38 | const MockComponent: React.FC<{ show: boolean }> = ({ show }) => { 39 | const { state, getters, dispatch } = useStore() 40 | 41 | const count = state.count + 1 42 | 43 | const logs = state.logs.map((log) => `Test typescript ${log}`) 44 | 45 | const countPlusTwo = getters.countPlusOne + 1 46 | 47 | dispatch({ 48 | type: "add", 49 | payload: count + countPlusTwo, 50 | }) 51 | 52 | dispatch({ 53 | type: "log", 54 | payload: logs.join(","), 55 | }) 56 | 57 | return show ? null : Hello react-vuex-hook 58 | } 59 | 60 | const Connected = connect(MockComponent) 61 | 62 | ReactDOM.render( 63 | , 64 | document.getElementById("typescript"), 65 | ) 66 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useMemo} from 'react' 2 | import {Spin, Button, Card} from 'antd' 3 | import {connect, useStore} from './store' 4 | import './index.css' 5 | import 'antd/dist/antd.css' 6 | 7 | function Count() { 8 | const {state, getters, dispatch} = useStore() 9 | const {countPlusOne} = getters 10 | const {loadingMap, count} = state 11 | // loadingMap是内部提供的变量 会监听异步action的起始和结束 12 | // 便于页面显示loading状态 13 | // 需要传入对应action的key值 14 | // 数组内可以写多项同时监听多个action 15 | // 灵感来源于dva 16 | const loading = loadingMap.any(['asyncAdd']) 17 | // 同步的add 18 | const add = () => dispatch({type: 'add'}) 19 | 20 | // 异步的add 21 | const asyncAdd = () => dispatch.action({type: 'asyncAdd'}) 22 | return ( 23 |
24 | 25 | 26 |
27 | store中的count现在是 {count} 28 |
29 |
30 | 33 | 36 |
37 |
38 | 通过getters计算出来的countPlusOne是 {countPlusOne} 39 |
40 | 41 | {/** 性能优化的做法 * */} 42 | {useMemo( 43 | () => ( 44 |
45 | 只有count变化会重新渲染 {count} 46 |
47 | ), 48 | [count], 49 | )} 50 |
51 |
52 |
53 | ) 54 | } 55 | 56 | // 必须用connect包裹 内部会保证Context的Provider在包裹Count的外层 57 | export default connect(Count) 58 | -------------------------------------------------------------------------------- /example/Example.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react'; 2 | import { Card, Button, Input } from 'antd'; 3 | import { connect, useStore } from './store'; 4 | import './index.css'; 5 | import 'antd/dist/antd.css'; 6 | 7 | let addLogHack = (val: string) => {}; 8 | 9 | function Count() { 10 | const { 11 | state: { count }, 12 | dispatch, 13 | } = useStore(); 14 | // 同步的add 15 | const add = useCallback(() => dispatch({ type: 'add' }), []); 16 | 17 | addLogHack('计数器组件重新渲染🚀'); 18 | 19 | return ( 20 | 21 |

计数器

22 |
23 |
store中的count现在是 {count}
24 | 25 |
26 |
27 | ); 28 | } 29 | 30 | function Chat() { 31 | const { 32 | state: { message }, 33 | dispatch, 34 | } = useStore(); 35 | 36 | addLogHack('聊天室组件重新渲染💐'); 37 | 38 | const onChange = (e: React.ChangeEvent) => { 39 | dispatch({ 40 | type: 'chat', 41 | payload: e.target.value 42 | }) 43 | } 44 | 45 | return ( 46 | 47 |

聊天室

48 | 当前消息是: {message} 49 | 50 |
51 | ); 52 | } 53 | 54 | function Logger() { 55 | const [logs, setLogs] = useState([]); 56 | addLogHack = (log: string) => setLogs(prevLogs => [log, ...prevLogs]); 57 | return ( 58 | 59 |

控制台

60 |
61 | {logs.map((log, idx) => ( 62 |

63 | {log} 64 |

65 | ))} 66 |
67 |
68 | ); 69 | } 70 | 71 | export default connect(() => { 72 | return ( 73 |
74 |
75 |
76 | 77 |
78 |
79 | 80 |
81 |
82 |
83 | 84 |
85 |
86 | ); 87 | }); 88 | -------------------------------------------------------------------------------- /lib/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IMutationsValue { 3 | (state: State, payload: any): State; 4 | } 5 | export interface IGettersValue { 6 | (state: State): any; 7 | } 8 | export declare type IState = State & { 9 | loadingMap: ILoadingMap; 10 | }; 11 | export declare type ILoadingMap = Record & { 12 | any: (keys: ActionsKey | ActionsKey[]) => boolean; 13 | }; 14 | interface IActionContext { 15 | state: State; 16 | getters: Record>; 17 | dispatch: IDispatch; 18 | } 19 | export declare type IActionsOption = Record, payload: any) => Promise>; 20 | export interface IOptions { 21 | getInitState: () => State; 22 | mutations: Record>; 23 | getters: IGetters; 24 | actions: IActionsOption; 25 | } 26 | export interface IConnect { 27 | (Component: React.ComponentType): React.FC; 28 | } 29 | export interface IDispatchArgs { 30 | type: MutationsKey; 31 | payload?: any; 32 | } 33 | export interface IDispatchActionArgs { 34 | type: ActionsKey; 35 | payload?: any; 36 | } 37 | export declare type IDispatch = React.Dispatch> & { 38 | action: (args: IDispatchActionArgs) => Promise; 39 | }; 40 | export declare type IGetters = Record>; 41 | export interface IContext { 42 | dispatch: IDispatch; 43 | state: IState; 44 | getters: IGetters; 45 | } 46 | export {}; 47 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export interface IMutationsValue { 4 | (state: State, payload: any): State 5 | } 6 | 7 | export interface IGettersValue { 8 | (state: State): any 9 | } 10 | 11 | export type IState = State & { 12 | loadingMap: ILoadingMap 13 | } 14 | 15 | export type ILoadingMap = Record< 16 | ActionsKey, 17 | boolean 18 | > & { 19 | any: (keys: ActionsKey | ActionsKey[]) => boolean 20 | } 21 | 22 | interface IActionContext< 23 | State, 24 | MutationsKey, 25 | GettersKey extends string, 26 | ActionsKey extends string 27 | > { 28 | state: State 29 | getters: IGettersResult 30 | dispatch: IDispatch 31 | } 32 | 33 | export type IActionsOption< 34 | State, 35 | MutationsKey extends string, 36 | GettersKey extends string, 37 | ActionsKey extends string 38 | > = Record< 39 | ActionsKey, 40 | ( 41 | context: IActionContext, 42 | payload: any, 43 | ) => Promise 44 | > 45 | 46 | export interface IOptions< 47 | State, 48 | MutationsKey extends string, 49 | GettersKey extends string, 50 | ActionsKey extends string 51 | > { 52 | getInitState: () => State 53 | mutations: Record> 54 | getters: IGetters 55 | actions: IActionsOption 56 | } 57 | 58 | export interface IConnect { 59 | (Component: React.ComponentType): React.FC 60 | } 61 | 62 | export interface IDispatchArgs { 63 | type: MutationsKey 64 | payload?: any 65 | } 66 | 67 | export interface IDispatchActionArgs { 68 | type: ActionsKey 69 | payload?: any 70 | } 71 | 72 | export type IDispatch = React.Dispatch< 73 | IDispatchArgs 74 | > & { 75 | action: (args: IDispatchActionArgs) => Promise 76 | } 77 | 78 | export type IGetters = Record< 79 | GettersKey, 80 | IGettersValue 81 | > 82 | 83 | export type IGettersResult = { 84 | [K in GettersKey]: any 85 | } 86 | 87 | export interface IContext< 88 | State, 89 | MutationsKey, 90 | GettersKey extends string, 91 | ActionsKey extends string 92 | > { 93 | dispatch: IDispatch 94 | state: IState 95 | getters: IGettersResult 96 | } 97 | -------------------------------------------------------------------------------- /example/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: 使用说明 3 | route: / 4 | --- 5 | 6 | import { Playground } from 'docz' 7 | import Count from '../example' 8 | 9 | # react-vuex-hook 10 | 11 | react-vuex-hook 是利用 React Hook 配合 Context 和 useReducer 封装的一个用于小型模块的状态管理库,提供类似 vuex 的语法。 12 | 13 | ## 安装 14 | 15 | ``` 16 | npm install react-vuex-hook -S 17 | ``` 18 | 19 | ## 更新 2.0 20 | 21 | 1. 新增测试用例,测试覆盖率达到 100% 22 | 23 | 2. `mutation` 的函数参数顺序和 Vuex 保持一致 24 | 25 | ```js 26 | mutations: { 27 | // 浅拷贝state 28 | add(state, payload) { 29 | return Object.assign({}, state, { count: state.count + 1 }) 30 | }, 31 | }, 32 | ``` 33 | 34 | 3. `actions` 的函数参数和 vuex 保持一致 35 | 36 | ```js 37 | actions: { 38 | async asyncAdd({ dispatch, state, getters }, payload) { 39 | await wait(100) 40 | dispatch({ type: 'add' }) 41 | // 返回的值会被包裹的promise resolve 42 | return true 43 | }, 44 | }, 45 | ``` 46 | 47 | ### 适用场景 48 | 49 | 比较适用于单个比较复杂的小模块,个人认为这也是 react 官方推荐 useReducer 和 context 配合使用的场景。 50 | 由于所有使用了 useContext 的组件都会在 state 发生变化的时候进行更新(context 的弊端),推荐渲染复杂场景的时候配合 useMemo 来做性能优化。 51 | 52 | ### 编写 store 53 | 54 | ```javascript 55 | // store.js 56 | import initStore from 'react-vuex-hook' 57 | 58 | const store = { 59 | // 初始状态 60 | initState: { 61 | count: 0, 62 | }, 63 | // 同步操作 必须返回state的拷贝值 64 | mutations: { 65 | // 浅拷贝state 66 | add(state, payload) { 67 | return Object.assign({}, state, { count: state.count + 1 }) 68 | }, 69 | }, 70 | // 异步操作,拥有dispatch的执行权 71 | actions: { 72 | async asyncAdd({ dispatch, state, getters }, payload) { 73 | await wait(1000) 74 | dispatch({ type: 'add' }) 75 | // 返回的值会被包裹的promise resolve 76 | return true 77 | }, 78 | }, 79 | // 计算属性 根据state里的值动态计算 80 | // 在页面中根据state值的变化而动态变化 81 | getters: { 82 | countPlusOne(state) { 83 | return state.count + 1 84 | }, 85 | }, 86 | } 87 | 88 | export const { connect, useStore } = initStore(store) 89 | ``` 90 | 91 | ### 在页面引用 92 | 93 | ```javascript 94 | // page.js 95 | import React, { useMemo } from 'react' 96 | import { Spin } from 'antd' 97 | import { connect, useStore } from './store.js' 98 | 99 | function Count() { 100 | const { state, getters, dispatch } = useStore() 101 | const { countPlusOne } = getters 102 | const { loadingMap, count } = state 103 | // loadingMap是内部提供的变量 会监听异步action的起始和结束 104 | // 便于页面显示loading状态 105 | // 需要传入对应action的key值 106 | // 数组内可以写多项同时监听多个action 107 | // 灵感来源于dva 108 | const loading = loadingMap.any(['asyncAdd']) 109 | 110 | // 同步的add 111 | const add = () => dispatch({ type: 'add' }) 112 | 113 | // 异步的add 114 | const asyncAdd = () => dispatch.action({ type: 'asyncAdd' }) 115 | return ( 116 | 117 | count is {count} 118 | countPlusOne is {countPlusOne} 119 | 120 | 121 | 122 | {/** 性能优化的做法 * */} 123 | {useMemo( 124 | () => ( 125 | 只有count变化会重新渲染 {count} 126 | ), 127 | [count] 128 | )} 129 | 130 | ) 131 | } 132 | 133 | // 必须用connect包裹 内部会保证Context的Provider在包裹Count的外层 134 | export default connect(Count) 135 | ``` 136 | 137 | ### 效果 138 | 139 | 140 | -------------------------------------------------------------------------------- /example/Example.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: 需要优化的场景 3 | route: /example 4 | --- 5 | 6 | import { Playground } from 'docz'; 7 | import Example from '../example/Example'; 8 | 9 | ## 代码 10 | 11 | 由于 Context 的特性,在下面这种场景下,需要手动用 useMemo 进行优化。 12 | 13 | 详见 https://zh-hans.reactjs.org/docs/context.html#contextprovider 14 | 15 | > 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新 16 | 17 | ```tsx 18 | import React, { useState, useCallback } from 'react'; 19 | import { Card, Button, Input } from 'antd'; 20 | import { connect, useStore } from './store'; 21 | import './index.css'; 22 | import 'antd/dist/antd.css'; 23 | 24 | let addLogHack = (val: string) => {}; 25 | 26 | function Count() { 27 | const { 28 | state: { count }, 29 | dispatch, 30 | } = useStore(); 31 | // 同步的add 32 | const add = useCallback(() => dispatch({ type: 'add' }), []); 33 | 34 | addLogHack('计数器组件重新渲染🚀'); 35 | 36 | return ( 37 | 38 |

计数器

39 |
40 |
store中的count现在是 {count}
41 | 42 |
43 |
44 | ); 45 | } 46 | 47 | function Chat() { 48 | const { 49 | state: { message }, 50 | dispatch, 51 | } = useStore(); 52 | const [value, setValue] = useState(''); 53 | 54 | addLogHack('聊天室组件重新渲染💐'); 55 | 56 | const onChange = (e: React.ChangeEvent) => { 57 | dispatch({ 58 | type: 'chat', 59 | payload: e.target.value, 60 | }); 61 | }; 62 | 63 | return ( 64 | 65 |

聊天室

66 | 当前消息是: {message} 67 | 68 |
69 | ); 70 | } 71 | 72 | function Logger() { 73 | const [logs, setLogs] = useState([]); 74 | addLogHack = (log: string) => setLogs(prevLogs => [log, ...prevLogs]); 75 | return ( 76 | 77 |

控制台

78 |
79 | {logs.map((log, idx) => ( 80 |

81 | {log} 82 |

83 | ))} 84 |
85 |
86 | ); 87 | } 88 | 89 | export default connect(() => { 90 | return ( 91 |
92 |
93 |
94 | 95 |
96 |
97 | 98 |
99 |
100 |
101 | 102 |
103 |
104 | ); 105 | }); 106 | ``` 107 | 108 | ### 效果 109 | 110 | 111 | 112 | 113 | 114 | ### 优化方案 115 | 116 | ```tsx 117 | function Chat() { 118 | const { 119 | state: { message }, 120 | dispatch, 121 | } = useStore(); 122 | 123 | const onChange = (e: React.ChangeEvent) => { 124 | dispatch({ 125 | type: 'chat', 126 | payload: e.target.value, 127 | }); 128 | }; 129 | 130 | return React.useMemo( 131 | () => { 132 | addLogHack('聊天室组件重新渲染💐'); 133 | return ( 134 | 135 |

聊天室

136 | 当前消息是: {message} 137 | 138 |
139 | ) 140 | }, 141 | [message], 142 | ) 143 | } 144 | ``` 145 | 146 | 注意这种优化下,`Chat`组件还会重新运行,但是return的jsx在`message`不发生改变的情况下不会改变,所以也不会有耗费性能的reconciler流程了。 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-vuex-hook 2 | react-vuex-hook是利用React Hook配合Context和useReducer封装的一个用于小型模块的状态管理库,提供类似vuex的语法。 3 | 4 | react-vuex-hook is a state management library for small modules using React Hook with Context and useReducer, providing vuex-like syntax. 5 | 6 | ## 稳定性 7 | [![Build Status](https://travis-ci.org/sl1673495/react-vuex-hook.svg?branch=master)](https://travis-ci.org/sl1673495/react-vuex-hook) 8 | [![Coverage Status](https://coveralls.io/repos/github/sl1673495/reax-hook/badge.svg?branch=master)](https://coveralls.io/github/sl1673495/reax-hook?branch=master) 9 | 10 | ## 安装 11 | ``` 12 | npm install react-vuex-hook -S 13 | ``` 14 | 15 | ## 使用 16 | 17 | ### 编写 store 18 | 19 | ```javascript 20 | // store.js 21 | import initStore from 'react-vuex-hook' 22 | 23 | export const { connect, useStore } = initStore({ 24 | // 初始状态 25 | getInitState: () => ({ 26 | count: 0 27 | }), 28 | // 同步操作 必须返回state的拷贝值 29 | mutations: { 30 | // 浅拷贝state 31 | add(state, payload) { 32 | return Object.assign({}, state, { count: state.count + 1 }); 33 | } 34 | }, 35 | // 异步操作,拥有dispatch的执行权 36 | actions: { 37 | async asyncAdd({ dispatch, state, getters }, payload) { 38 | await wait(1000); 39 | dispatch({ type: "add" }); 40 | // 返回的值会被包裹的promise resolve 41 | return true; 42 | } 43 | }, 44 | // 计算属性 根据state里的值动态计算 45 | // 在页面中根据state值的变化而动态变化 46 | getters: { 47 | countPlusOne(state) { 48 | return state.count + 1; 49 | } 50 | } 51 | }); 52 | 53 | 54 | 55 | // 注意这里不要提前声明好配置对象然后传递给initStore 56 | // 否则由于ts的限制会失去类型推断 57 | ``` 58 | 59 | ### 在页面引用 60 | 61 | ```javascript 62 | // page.js 63 | import React, { useMemo } from 'react' 64 | import { Spin } from 'antd' 65 | import { connect, useStore } from './store.js' 66 | 67 | function Count() { 68 | const { state, getters, dispatch } = useStore() 69 | const { countPlusOne } = getters 70 | const { loadingMap, count } = state 71 | // loadingMap是内部提供的变量 会监听异步action的起始和结束 72 | // 便于页面显示loading状态 73 | // 需要传入对应action的key值 74 | // 数组内可以写多项同时监听多个action 75 | // 灵感来源于dva 76 | const loading = loadingMap.any(['asyncAdd']) 77 | 78 | // 同步的add 79 | const add = () => dispatch({ type: 'add' }) 80 | 81 | // 异步的add 82 | const asyncAdd = () => dispatch.action({ type: 'asyncAdd' }) 83 | return ( 84 | 85 | count is {count} 86 | countPlusOne is {countPlusOne} 87 | 88 | 89 | 90 | {/** 性能优化的做法 * */} 91 | {useMemo( 92 | () => ( 93 | 只有count变化会重新渲染 {count} 94 | ), 95 | [count] 96 | )} 97 | 98 | ) 99 | } 100 | 101 | // 必须用connect包裹 内部会保证Context的Provider在包裹Count的外层 102 | export default connect(Count) 103 | ``` 104 | 105 | ### 适用场景 106 | 107 | 比较适用于单个比较复杂的小模块,个人认为这也是 react 官方推荐 useReducer 和 context 配合使用的场景。 108 | 由于所有使用了 useContext 的组件都会在 state 发生变化的时候进行更新(context 的弊端),推荐渲染复杂场景的时候配合 useMemo 来做性能优化。 109 | 110 | 111 | ## 为什么用它 112 | 1. 带给你Vuex类似的语法。 113 | 2. 完善的TypeScript类型推导。 114 | 3. 测试覆盖率100% 115 | 116 | ## 文档 117 | 118 | https://sl1673495.github.io/react-vuex-hook/ 119 | 120 | ## 更新 5.0 121 | 1. 为了避免state复杂嵌套数据的污染,initState初始化的传入值由initState改为getInitState方法 122 | 123 | ## 更新 3.0 124 | 125 | 1. 全面使用TypeScript重构 126 | 127 | 2. 脚手架工具使用umi团队的`father` 128 | 129 | 3. 基于docz的文档 130 | 131 | ## 更新 2.0 132 | 133 | 1. 新增测试用例,测试覆盖率达到100% 134 | 135 | 2. `mutation` 的函数参数顺序和 Vuex 保持一致 136 | 137 | ```js 138 | mutations: { 139 | // 浅拷贝state 140 | add(state, payload) { 141 | return Object.assign({}, state, { count: state.count + 1 }) 142 | }, 143 | }, 144 | ``` 145 | 146 | 3. `actions` 的函数参数和vuex保持一致 147 | ```js 148 | actions: { 149 | async asyncAdd({ dispatch, state, getters }, payload) { 150 | await wait(100) 151 | dispatch({ type: 'add' }) 152 | // 返回的值会被包裹的promise resolve 153 | return true 154 | }, 155 | }, 156 | ``` 157 | -------------------------------------------------------------------------------- /test/index.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {cleanup, render, fireEvent} from '@testing-library/react' 3 | import {renderHook, act} from '@testing-library/react-hooks' 4 | import initStore from '../src' 5 | 6 | const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time)) 7 | 8 | const {connect, useStore} = initStore({ 9 | getInitState: () => ({ 10 | count: 1, 11 | }), 12 | mutations: { 13 | // 浅拷贝state 14 | add(state, payload) { 15 | return Object.assign({}, state, {count: state.count + 1}) 16 | }, 17 | }, 18 | getters: { 19 | countPlusOne(state) { 20 | return state.count + 1 21 | }, 22 | }, 23 | actions: { 24 | async asyncAdd({dispatch, state, getters}, payload) { 25 | await wait(0) 26 | dispatch({type: 'add'}) 27 | // 返回的值会被包裹的promise resolve 28 | return true 29 | }, 30 | async asyncError() { 31 | throw new Error('async error') 32 | }, 33 | }, 34 | }) 35 | 36 | function Comp({children = null}) { 37 | const {state, getters, dispatch} = useStore() 38 | const onAdd = () => dispatch({type: 'add'}) 39 | const onErrorType = () => dispatch({type: 'ERROR_TYPE'} as any) 40 | const asyncAdd = () => dispatch.action({type: 'asyncAdd'}) 41 | const asyncError = () => dispatch.action({type: 'asyncError'}) 42 | return ( 43 |
44 | {state.count} 45 | {getters.countPlusOne} 46 | 47 | 48 | 49 | 50 | {children} 51 |
52 | ) 53 | } 54 | 55 | afterEach(cleanup) 56 | // 调用connect包裹需要使用useStore的组件 57 | const Connected = connect(Comp) 58 | 59 | describe('connect to a component', () => { 60 | test('should state changed by mutation', () => { 61 | const {getByTestId} = render() 62 | expect(getByTestId('count').innerHTML).toBe('1') 63 | fireEvent.click(getByTestId('add')) 64 | expect(getByTestId('count').innerHTML).toBe('2') 65 | }) 66 | 67 | test('should getter changed by mutation', () => { 68 | const {getByTestId} = render() 69 | expect(getByTestId('countPlusOne').innerHTML).toBe('2') 70 | fireEvent.click(getByTestId('add')) 71 | expect(getByTestId('countPlusOne').innerHTML).toBe('3') 72 | }) 73 | 74 | test('should state and getter changed by async action', async () => { 75 | const {result} = renderHook(() => useStore(), {wrapper: Connected}) 76 | const {dispatch} = result.current 77 | 78 | await act(async () => { 79 | await dispatch.action({ 80 | type: 'asyncAdd', 81 | }) 82 | 83 | const {state, getters} = result.current 84 | expect(state.count).toBe(2) 85 | expect(getters.countPlusOne).toBe(3) 86 | }) 87 | }) 88 | 89 | test('should correctly change loading state before and after async action', async () => { 90 | const {result, waitForNextUpdate} = renderHook(() => useStore(), { 91 | wrapper: Connected, 92 | }) 93 | const {dispatch} = result.current 94 | expect(result.current.state.loadingMap.any(['asyncAdd'])).toBeFalsy() 95 | await act(async () => { 96 | dispatch.action({ 97 | type: 'asyncAdd', 98 | }) 99 | await waitForNextUpdate() 100 | expect(result.current.state.loadingMap.any('asyncAdd')).toBeTruthy() 101 | await waitForNextUpdate() 102 | expect(result.current.state.loadingMap.any(['asyncAdd'])).toBeFalsy() 103 | }) 104 | }) 105 | 106 | test('should throw an exception when calling an error type', async () => { 107 | const {result} = renderHook(() => useStore(), {wrapper: Connected}) 108 | const {dispatch} = result.current 109 | 110 | const throwFn = async () => 111 | await dispatch.action({type: 'ERROR_TYPE'} as any) 112 | await act(async () => { 113 | try { 114 | await throwFn() 115 | } catch (e) { 116 | expect(e).toBeInstanceOf(Error) 117 | } 118 | }) 119 | }) 120 | 121 | test('should work with empty args', () => { 122 | const {connect} = initStore() 123 | const EmptyConnect = connect(() => { 124 | return empty 125 | }) 126 | const {getByTestId} = render() 127 | expect(getByTestId('empty').innerHTML).toBe('empty') 128 | }) 129 | 130 | test('should not crash when calling loadingMap.any for a non-existent action type', async () => { 131 | const {result} = renderHook(() => useStore(), {wrapper: Connected}) 132 | expect(result.current.state.loadingMap.any(['ERROR_TYPE'] as any)).toBe( 133 | false, 134 | ) 135 | }) 136 | }) 137 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import hoistStatics from 'hoist-non-react-statics' 3 | import propTypes from 'prop-types' 4 | import { 5 | IState, 6 | IGetters, 7 | IGettersResult, 8 | IOptions, 9 | IDispatchArgs, 10 | IDispatch, 11 | IContext, 12 | IMutationsValue, 13 | } from './types' 14 | 15 | const {useReducer, useContext, useMemo} = React 16 | // reax: 创建一个小型的store 17 | export default function initStore< 18 | State extends {}, 19 | MutationsKey extends string, 20 | GettersKey extends string, 21 | ActionsKey extends string 22 | >(options?: IOptions) { 23 | const { 24 | getInitState = () => undefined as () => State, 25 | mutations: rawMutations = {} as Record< 26 | MutationsKey, 27 | IMutationsValue 28 | >, 29 | actions: rawActions = {}, 30 | getters: rawGetters = {} as IGetters, 31 | } = options || {} 32 | 33 | const mutations = mixinChangeLoading(rawMutations) 34 | 35 | const reducer = (state: State, action: IDispatchArgs) => { 36 | const {type, payload} = action 37 | const mutation = mutations[type] 38 | // 用于开发环境时提示拼写错误,可以不计入测试覆盖率 39 | /* istanbul ignore if */ 40 | if (typeof mutation !== 'function') { 41 | typeError(type) 42 | } 43 | return mutation(state, payload) 44 | } 45 | 46 | const Context = React.createContext< 47 | IContext, MutationsKey, GettersKey, ActionsKey> 48 | >(null) 49 | 50 | const Provider = (props: {children: React.ReactNode}) => { 51 | const {children} = props 52 | const [state, dispatch] = useReducer(reducer, undefined, getInitState) 53 | // 计算一把computed 54 | const computedGetters = useMemo( 55 | () => initGetter(rawGetters, state), 56 | [state], 57 | ) 58 | // 让actions执行前后改变loading 59 | const actions = useMemo(() => initActions(rawActions, dispatch), []) 60 | const dispatchAction = useMemo( 61 | () => initDispatchAction(dispatch, actions, state, computedGetters), 62 | [actions, computedGetters, state], 63 | ) 64 | // dispatchAction没法做到引用保持不变 所以挂到dispatch上 65 | // 这样用户使用useEffect把dispatch作为依赖 就不会造成无限更新 66 | const withDispatchAction: IDispatch< 67 | MutationsKey, 68 | ActionsKey 69 | > = dispatch as any 70 | withDispatchAction.action = dispatchAction 71 | 72 | // 重命名state 用于强制推断类型 73 | const reducerState: IState = state as any 74 | // 给loadingMap加上一些api 75 | const enhancedState = enhanceLoadingMap(reducerState) 76 | return ( 77 | 84 | {children} 85 | 86 | ) 87 | } 88 | 89 | Provider.propTypes = { 90 | children: propTypes.element.isRequired, 91 | } 92 | 93 | const connect =

(Component: React.ComponentType

) => { 94 | const WrapWithProvider = (props: any) => ( 95 | 96 | 97 | 98 | ) 99 | return argumentContainer(WrapWithProvider, Component) as React.FC

100 | } 101 | 102 | const useStore = () => useContext(Context) 103 | return {connect, useStore} 104 | } 105 | 106 | const CHANGE_LOADING = '@@changeLoadingState' 107 | // 加入改变loading状态的方法 108 | function mixinChangeLoading(mutations) { 109 | return Object.assign({}, mutations, { 110 | [CHANGE_LOADING](state, payload) { 111 | const {actionKey, isLoading} = payload 112 | const {loadingMap} = state 113 | const newLoadingMap = { 114 | ...loadingMap, 115 | [actionKey]: isLoading, 116 | } 117 | return { 118 | ...state, 119 | loadingMap: newLoadingMap, 120 | } 121 | }, 122 | }) 123 | } 124 | 125 | // 通过最新的state把getter计算出来 126 | function initGetter( 127 | rawGetters: IGetters, 128 | state: State, 129 | ) { 130 | const getters = {} as IGettersResult 131 | const rawGetterKeys = Object.keys(rawGetters) 132 | rawGetterKeys.forEach(rawGetterKey => { 133 | const rawGetter = rawGetters[rawGetterKey] 134 | const result = rawGetter(state) 135 | getters[rawGetterKey] = result 136 | }) 137 | return getters 138 | } 139 | 140 | // 劫持原有的action方法 在action执行前后更改loading状态 141 | function initActions(rawActions: {}, dispatch: React.Dispatch) { 142 | const changeLoading = (actionKey: string, isLoading: boolean) => { 143 | dispatch({ 144 | type: CHANGE_LOADING, 145 | payload: { 146 | isLoading, 147 | actionKey, 148 | }, 149 | }) 150 | } 151 | 152 | const actions = {} 153 | const rawActionKeys = Object.keys(rawActions) 154 | rawActionKeys.forEach(rawActionKey => { 155 | actions[rawActionKey] = async (...actionArgs) => { 156 | changeLoading(rawActionKey, true) 157 | const result = await rawActions[rawActionKey](...actionArgs) 158 | changeLoading(rawActionKey, false) 159 | return result 160 | } 161 | }) 162 | 163 | return actions 164 | } 165 | 166 | // dispatch actions里的方法 返回promise 167 | function initDispatchAction(dispatch, actions, state, getters) { 168 | return ({type, payload}) => 169 | new Promise((resolve, reject) => { 170 | if (typeof actions[type] === 'function') { 171 | actions[type]({dispatch, state, getters}, payload) 172 | .then(resolve) 173 | .catch(reject) 174 | } else { 175 | typeError(type) 176 | } 177 | }) 178 | } 179 | 180 | function enhanceLoadingMap( 181 | state: IState, 182 | ): IState { 183 | if (state) { 184 | if (!state.loadingMap) { 185 | state.loadingMap = {} as any 186 | } 187 | const {loadingMap} = state 188 | loadingMap.any = keys => { 189 | keys = Array.isArray(keys) ? keys : [keys] 190 | return keys.some(key => !!loadingMap[key]) 191 | } 192 | return state 193 | } 194 | } 195 | 196 | function typeError(type: string) { 197 | throw new Error(`error action type:${type}`) 198 | } 199 | 200 | function getDisplayName(WrappedComponent: React.ComponentType) { 201 | return ( 202 | WrappedComponent.displayName || WrappedComponent.name || 'WrappedComponent' 203 | ) 204 | } 205 | 206 | function argumentContainer( 207 | Container: React.ComponentType & {WrappedComponent?: React.ComponentType}, 208 | WrappedComponent: React.ComponentType, 209 | ) { 210 | // 给组件增加displayName 211 | Container.displayName = `Store(${getDisplayName(WrappedComponent)})` 212 | // 增加被包裹组件的引用 213 | Container.WrappedComponent = WrappedComponent 214 | return hoistStatics(Container, WrappedComponent) 215 | } 216 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = initStore; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _hoistNonReactStatics = _interopRequireDefault(require("hoist-non-react-statics")); 11 | 12 | var _propTypes = _interopRequireDefault(require("prop-types")); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 15 | 16 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } 17 | 18 | function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } 19 | 20 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } 21 | 22 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 23 | 24 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 25 | 26 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } 27 | 28 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } 29 | 30 | function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } 31 | 32 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 33 | 34 | var useReducer = _react["default"].useReducer, 35 | useContext = _react["default"].useContext, 36 | useMemo = _react["default"].useMemo; // reax: 创建一个小型的store 37 | 38 | function initStore(options) { 39 | var _ref = options || {}, 40 | getInitState = _ref.getInitState, 41 | _ref$mutations = _ref.mutations, 42 | mutations = _ref$mutations === void 0 ? {} : _ref$mutations, 43 | _ref$actions = _ref.actions, 44 | rawActions = _ref$actions === void 0 ? {} : _ref$actions, 45 | _ref$getters = _ref.getters, 46 | rawGetters = _ref$getters === void 0 ? {} : _ref$getters; 47 | 48 | mixinMutations(mutations); 49 | 50 | var reducer = function reducer(state, action) { 51 | var type = action.type, 52 | payload = action.payload; 53 | var mutation = mutations[type]; // 用于开发环境时提示拼写错误,可以不计入测试覆盖率 54 | 55 | /* istanbul ignore if */ 56 | 57 | if (typeof mutation !== 'function') { 58 | typeError(type); 59 | } 60 | 61 | return mutation(state, payload); 62 | }; 63 | 64 | var Context = _react["default"].createContext(null); 65 | 66 | var Provider = function Provider(props) { 67 | var children = props.children; 68 | 69 | var _useReducer = useReducer(reducer, undefined, getInitState), 70 | _useReducer2 = _slicedToArray(_useReducer, 2), 71 | state = _useReducer2[0], 72 | dispatch = _useReducer2[1]; // 计算一把computed 73 | 74 | 75 | var computedGetters = useMemo(function () { 76 | return initGetter(rawGetters, state); 77 | }, [state]); // 让actions执行前后改变loading 78 | 79 | var actions = useMemo(function () { 80 | return initActions(rawActions, dispatch); 81 | }, []); 82 | var dispatchAction = useMemo(function () { 83 | return initDispatchAction(dispatch, actions, state, computedGetters); 84 | }, [actions, computedGetters, state]); // dispatchAction没法做到引用保持不变 所以挂到dispatch上 85 | // 这样用户使用useEffect把dispatch作为依赖 就不会造成无限更新 86 | 87 | var withDispatchAction = dispatch; 88 | withDispatchAction.action = dispatchAction; // 重命名state 用于强制推断类型 89 | 90 | var reducerState = state; // 给loadingMap加上一些api 91 | 92 | var enhancedState = enhanceLoadingMap(reducerState); 93 | return _react["default"].createElement(Context.Provider, { 94 | value: { 95 | state: enhancedState, 96 | dispatch: withDispatchAction, 97 | getters: computedGetters 98 | } 99 | }, children); 100 | }; 101 | 102 | Provider.propTypes = { 103 | children: _propTypes["default"].element.isRequired 104 | }; 105 | 106 | var connect = function connect(Component) { 107 | var WrapWithProvider = function WrapWithProvider(props) { 108 | return _react["default"].createElement(Provider, null, _react["default"].createElement(Component, Object.assign({}, props))); 109 | }; // 加上displayName 110 | // 拷贝静态属性 111 | 112 | 113 | return argumentContainer(WrapWithProvider, Component); 114 | }; 115 | 116 | var useStore = function useStore() { 117 | return useContext(Context); 118 | }; 119 | 120 | return { 121 | connect: connect, 122 | useStore: useStore 123 | }; 124 | } 125 | 126 | var CHANGE_LOADING = '@@changeLoadingState'; // 加入改变loading状态的方法 127 | 128 | function mixinMutations(mutations) { 129 | return Object.assign(mutations, _defineProperty({}, CHANGE_LOADING, function (state, payload) { 130 | var actionKey = payload.actionKey, 131 | isLoading = payload.isLoading; 132 | var loadingMap = state.loadingMap; 133 | 134 | var newLoadingMap = _objectSpread({}, loadingMap, _defineProperty({}, actionKey, isLoading)); 135 | 136 | return _objectSpread({}, state, { 137 | loadingMap: newLoadingMap 138 | }); 139 | })); 140 | } // 通过最新的state把getter计算出来 141 | 142 | 143 | function initGetter(rawGetters, state) { 144 | var getters = {}; 145 | var rawGetterKeys = Object.keys(rawGetters); 146 | rawGetterKeys.forEach(function (rawGetterKey) { 147 | var rawGetter = rawGetters[rawGetterKey]; 148 | var result = rawGetter(state); 149 | getters[rawGetterKey] = result; 150 | }); 151 | return getters; 152 | } // 劫持原有的action方法 在action执行前后更改loading状态 153 | 154 | 155 | function initActions(rawActions, dispatch) { 156 | var changeLoading = function changeLoading(actionKey, isLoading) { 157 | return dispatch({ 158 | type: CHANGE_LOADING, 159 | payload: { 160 | isLoading: isLoading, 161 | actionKey: actionKey 162 | } 163 | }); 164 | }; 165 | 166 | var actions = {}; 167 | var rawActionKeys = Object.keys(rawActions); 168 | rawActionKeys.forEach(function (rawActionKey) { 169 | actions[rawActionKey] = 170 | /*#__PURE__*/ 171 | _asyncToGenerator( 172 | /*#__PURE__*/ 173 | regeneratorRuntime.mark(function _callee() { 174 | var result, 175 | _args = arguments; 176 | return regeneratorRuntime.wrap(function _callee$(_context) { 177 | while (1) { 178 | switch (_context.prev = _context.next) { 179 | case 0: 180 | changeLoading(rawActionKey, true); 181 | _context.next = 3; 182 | return rawActions[rawActionKey].apply(rawActions, _args); 183 | 184 | case 3: 185 | result = _context.sent; 186 | changeLoading(rawActionKey, false); 187 | return _context.abrupt("return", result); 188 | 189 | case 6: 190 | case "end": 191 | return _context.stop(); 192 | } 193 | } 194 | }, _callee); 195 | })); 196 | }); 197 | return actions; 198 | } // dispatch actions里的方法 199 | // 返回promise 200 | 201 | 202 | function initDispatchAction(dispatch, actions, state, getters) { 203 | return function (_ref3) { 204 | var type = _ref3.type, 205 | payload = _ref3.payload; 206 | return new Promise(function (resolve, reject) { 207 | if (typeof actions[type] === 'function') { 208 | actions[type]({ 209 | dispatch: dispatch, 210 | state: state, 211 | getters: getters 212 | }, payload).then(resolve); 213 | } else { 214 | typeError(type); 215 | } 216 | }); 217 | }; 218 | } 219 | 220 | function enhanceLoadingMap(state) { 221 | if (state) { 222 | if (!state.loadingMap) { 223 | state.loadingMap = {}; 224 | } 225 | 226 | var loadingMap = state.loadingMap; 227 | 228 | loadingMap.any = function (keys) { 229 | keys = Array.isArray(keys) ? keys : [keys]; 230 | return keys.some(function (key) { 231 | return !!loadingMap[key]; 232 | }); 233 | }; 234 | 235 | return state; 236 | } 237 | } 238 | 239 | function typeError(type) { 240 | throw new Error("error action type:".concat(type)); 241 | } 242 | 243 | function getDisplayName(WrappedComponent) { 244 | return WrappedComponent.displayName || WrappedComponent.name || 'WrappedComponent'; 245 | } 246 | 247 | function argumentContainer(Container, WrappedComponent) { 248 | // 给组件增加displayName 249 | Container.displayName = "Store(".concat(getDisplayName(WrappedComponent), ")"); // 增加被包裹组件的引用 250 | 251 | Container.WrappedComponent = WrappedComponent; 252 | return (0, _hoistNonReactStatics["default"])(Container, WrappedComponent); 253 | } -------------------------------------------------------------------------------- /docs/static/js/example-index.2458096f.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[3],{"./example/index.css":function(e,n,t){},"./example/index.mdx":function(e,n,t){"use strict";t.r(n);var a=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/extends.js"),o=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/objectWithoutProperties.js"),r=t("react"),A=t.n(r),c=t("./node_modules/_@mdx-js_react@1.5.1@@mdx-js/react/dist/index.es.js"),s=t("./node_modules/_docz@1.2.0@docz/dist/index.esm.js"),i=t("./node_modules/_antd@3.25.0@antd/es/card/index.js"),u=t("./node_modules/_antd@3.25.0@antd/es/spin/index.js"),d=t("./node_modules/_antd@3.25.0@antd/es/button/index.js"),l=t("./example/store.ts");t("./example/index.css"),t("./node_modules/_antd@3.25.0@antd/dist/antd.css");var g=Object(l.a)((function(){var e=Object(l.b)(),n=e.state,t=e.getters,a=e.dispatch,o=t.countPlusOne,c=n.loadingMap,s=n.count,g=c.any(["asyncAdd"]);return A.a.createElement("section",{className:"wrap"},A.a.createElement(i.a,{hoverable:!0,style:{width:240}},A.a.createElement(u.a,{spinning:g},A.a.createElement("div",{className:"chunk"},A.a.createElement("div",{className:"chunk"},"store\u4e2d\u7684count\u73b0\u5728\u662f ",s),A.a.createElement(d.a,{onClick:function(){return a({type:"add"})}},"add"),A.a.createElement(d.a,{onClick:function(){return a.action({type:"asyncAdd"})}},"async add")),A.a.createElement("div",{className:"chunk"},A.a.createElement("span",null,"\u901a\u8fc7getters\u8ba1\u7b97\u51fa\u6765\u7684countPlusOne\u662f ",o)),Object(r.useMemo)((function(){return A.a.createElement("div",{className:"chunk"},A.a.createElement("span",null,"\u53ea\u6709count\u53d8\u5316\u4f1a\u91cd\u65b0\u6e32\u67d3 ",s))}),[s]))))}));t.d(n,"default",(function(){return m}));var b={},p="wrapper";function m(e){var n=e.components,t=Object(o.a)(e,["components"]);return Object(c.b)(p,Object(a.a)({},b,t,{components:n,mdxType:"MDXLayout"}),Object(c.b)("h1",{id:"react-vuex-hook"},"react-vuex-hook"),Object(c.b)("p",null,"react-vuex-hook \u662f\u5229\u7528 React Hook \u914d\u5408 Context \u548c useReducer \u5c01\u88c5\u7684\u4e00\u4e2a\u7528\u4e8e\u5c0f\u578b\u6a21\u5757\u7684\u72b6\u6001\u7ba1\u7406\u5e93\uff0c\u63d0\u4f9b\u7c7b\u4f3c vuex \u7684\u8bed\u6cd5\u3002"),Object(c.b)("h2",{id:"\u5b89\u88c5"},"\u5b89\u88c5"),Object(c.b)("pre",null,Object(c.b)("code",Object(a.a)({parentName:"pre"},{}),"npm install react-vuex-hook -S\n")),Object(c.b)("h2",{id:"\u66f4\u65b0-20"},"\u66f4\u65b0 2.0"),Object(c.b)("ol",null,Object(c.b)("li",{parentName:"ol"},Object(c.b)("p",{parentName:"li"},"\u65b0\u589e\u6d4b\u8bd5\u7528\u4f8b\uff0c\u6d4b\u8bd5\u8986\u76d6\u7387\u8fbe\u5230 100%")),Object(c.b)("li",{parentName:"ol"},Object(c.b)("p",{parentName:"li"},Object(c.b)("inlineCode",{parentName:"p"},"mutation")," \u7684\u51fd\u6570\u53c2\u6570\u987a\u5e8f\u548c Vuex \u4fdd\u6301\u4e00\u81f4"))),Object(c.b)("pre",null,Object(c.b)("code",Object(a.a)({parentName:"pre"},{className:"language-js"})," mutations: {\n // \u6d45\u62f7\u8d1dstate\n add(state, payload) {\n return Object.assign({}, state, { count: state.count + 1 })\n },\n },\n")),Object(c.b)("ol",{start:3},Object(c.b)("li",{parentName:"ol"},Object(c.b)("inlineCode",{parentName:"li"},"actions")," \u7684\u51fd\u6570\u53c2\u6570\u548c vuex \u4fdd\u6301\u4e00\u81f4")),Object(c.b)("pre",null,Object(c.b)("code",Object(a.a)({parentName:"pre"},{className:"language-js"})," actions: {\n async asyncAdd({ dispatch, state, getters }, payload) {\n await wait(100)\n dispatch({ type: 'add' })\n // \u8fd4\u56de\u7684\u503c\u4f1a\u88ab\u5305\u88f9\u7684promise resolve\n return true\n },\n },\n")),Object(c.b)("h3",{id:"\u9002\u7528\u573a\u666f"},"\u9002\u7528\u573a\u666f"),Object(c.b)("p",null,"\u6bd4\u8f83\u9002\u7528\u4e8e\u5355\u4e2a\u6bd4\u8f83\u590d\u6742\u7684\u5c0f\u6a21\u5757\uff0c\u4e2a\u4eba\u8ba4\u4e3a\u8fd9\u4e5f\u662f react \u5b98\u65b9\u63a8\u8350 useReducer \u548c context \u914d\u5408\u4f7f\u7528\u7684\u573a\u666f\u3002\n\u7531\u4e8e\u6240\u6709\u4f7f\u7528\u4e86 useContext \u7684\u7ec4\u4ef6\u90fd\u4f1a\u5728 state \u53d1\u751f\u53d8\u5316\u7684\u65f6\u5019\u8fdb\u884c\u66f4\u65b0(context \u7684\u5f0a\u7aef)\uff0c\u63a8\u8350\u6e32\u67d3\u590d\u6742\u573a\u666f\u7684\u65f6\u5019\u914d\u5408 useMemo \u6765\u505a\u6027\u80fd\u4f18\u5316\u3002"),Object(c.b)("h3",{id:"\u7f16\u5199-store"},"\u7f16\u5199 store"),Object(c.b)("pre",null,Object(c.b)("code",Object(a.a)({parentName:"pre"},{className:"language-javascript"}),"// store.js\nimport initStore from 'react-vuex-hook'\n\nconst store = {\n // \u521d\u59cb\u72b6\u6001\n initState: {\n count: 0,\n },\n // \u540c\u6b65\u64cd\u4f5c \u5fc5\u987b\u8fd4\u56destate\u7684\u62f7\u8d1d\u503c\n mutations: {\n // \u6d45\u62f7\u8d1dstate\n add(state, payload) {\n return Object.assign({}, state, { count: state.count + 1 })\n },\n },\n // \u5f02\u6b65\u64cd\u4f5c\uff0c\u62e5\u6709dispatch\u7684\u6267\u884c\u6743\n actions: {\n async asyncAdd({ dispatch, state, getters }, payload) {\n await wait(1000)\n dispatch({ type: 'add' })\n // \u8fd4\u56de\u7684\u503c\u4f1a\u88ab\u5305\u88f9\u7684promise resolve\n return true\n },\n },\n // \u8ba1\u7b97\u5c5e\u6027 \u6839\u636estate\u91cc\u7684\u503c\u52a8\u6001\u8ba1\u7b97\n // \u5728\u9875\u9762\u4e2d\u6839\u636estate\u503c\u7684\u53d8\u5316\u800c\u52a8\u6001\u53d8\u5316\n getters: {\n countPlusOne(state) {\n return state.count + 1\n },\n },\n}\n\nexport const { connect, useStore } = initStore(store)\n")),Object(c.b)("h3",{id:"\u5728\u9875\u9762\u5f15\u7528"},"\u5728\u9875\u9762\u5f15\u7528"),Object(c.b)("pre",null,Object(c.b)("code",Object(a.a)({parentName:"pre"},{className:"language-javascript"}),"// page.js\nimport React, { useMemo } from 'react'\nimport { Spin } from 'antd'\nimport { connect, useStore } from './store.js'\n\nfunction Count() {\n const { state, getters, dispatch } = useStore()\n const { countPlusOne } = getters\n const { loadingMap, count } = state\n // loadingMap\u662f\u5185\u90e8\u63d0\u4f9b\u7684\u53d8\u91cf \u4f1a\u76d1\u542c\u5f02\u6b65action\u7684\u8d77\u59cb\u548c\u7ed3\u675f\n // \u4fbf\u4e8e\u9875\u9762\u663e\u793aloading\u72b6\u6001\n // \u9700\u8981\u4f20\u5165\u5bf9\u5e94action\u7684key\u503c\n // \u6570\u7ec4\u5185\u53ef\u4ee5\u5199\u591a\u9879\u540c\u65f6\u76d1\u542c\u591a\u4e2aaction\n // \u7075\u611f\u6765\u6e90\u4e8edva\n const loading = loadingMap.any(['asyncAdd'])\n\n // \u540c\u6b65\u7684add\n const add = () => dispatch({ type: 'add' })\n\n // \u5f02\u6b65\u7684add\n const asyncAdd = () => dispatch.action({ type: 'asyncAdd' })\n return (\n \n count is {count}\n countPlusOne is {countPlusOne}\n \n \n\n {/** \u6027\u80fd\u4f18\u5316\u7684\u505a\u6cd5 * */}\n {useMemo(\n () => (\n \u53ea\u6709count\u53d8\u5316\u4f1a\u91cd\u65b0\u6e32\u67d3 {count}\n ),\n [count]\n )}\n \n )\n}\n\n// \u5fc5\u987b\u7528connect\u5305\u88f9 \u5185\u90e8\u4f1a\u4fdd\u8bc1Context\u7684Provider\u5728\u5305\u88f9Count\u7684\u5916\u5c42\nexport default connect(Count)\n")),Object(c.b)("h3",{id:"\u6548\u679c"},"\u6548\u679c"),Object(c.b)(s.c,{__position:0,__code:"",__scope:{props:this?this.props:t,Playground:s.c,Count:g},__codesandbox:"N4IgZglgNgpgziAXKCA7AJjAHgOgBYAuAtlEqAMYD2qBMNSIAPOhAG4AEE6AvADogAnSpQL8AfIwD0LVmJABfADQg0mXACsEyEFRp0CDSQCojvVO3YAVPBDjsAwpUwBlAIYYARpSzs8rux4wdOyuAK4ElESuBBDkrlBQAJ7sAOZ0MALRMOjsoXBoKWYWAAZUmHDu6F5YGcU47ACSYOyJlKEA5AIw7OShAhBtdniUAO7sBH4Evq4ADjPp6IotbT3uRT14MOQA1uxtU20C7OiU5EMZMIi-BAQzcIiSkikQE6EeOFREkmXwldXfTl-nm8GTM6yay1C7Gw836dHI3XcyTgBAEoRSKVgdhGLzw4xsdgqwKwSxmsH83UytkR5lscFCMHW1FSuLe9UQAEozEZJGYIEQZpQBFMAEowVzkKZgIREdidcWS9oAbj5AqFooVBAAIgB5ACy7GlkTlXQlBAAtCciMrVYLhQ5IoLUPpDTK5ThJNhXALYDbUGYxWbdXqcF0MBkABTrZhsMTrCyMPAAZjEjjVzponDVwuy7BxEx6gKJVRBAkQUmTcfMFnYjBmYgjDSmUWSztzrnQLBi1Hi7BmQju40oqRgUxe7DDmH6qBSeZgCQ5Unr8draadLskVYT0ljijMJ16RH0ODSBAAorAjzQAEKJBroCOdYQEdpc1AcpUKZRen0wHCaMgdGoWh6EQFRsymYB2AABSgVxEhSIRQgwdh5FdY12gPAAvP1-TtKZHGQqU3XaD0fzJGBcIg9hA0ldDZXlM12jBVBdBRY5Tiw5wCESLF2G4dhgHWKIBGeVArnaAAGdgkxmLB2j3asZg7FgZwkgAWOT2AANjkhT1jAYCADFvWgRIJP4ZxDgRB1MBgoR-CWIhqEoOBlIRfTq0MmhnAgLDLnYABGdTFPkFi2KmABBOZ-PYCMoPIGwoHQMNUI5fixDi6MZHYFFeJgbhgGw7j8rgeQtxrYBEugFLggAMjq8ZEnmShmmq5LUu4Lq5TAZDJQGVB2nYAB-DYarDCN0qudratQMLqykGQqzfMwYXVY4YDAMIoCmSaMqyhbouXasE1oggcCMzIUivAgKprVc2kzTcVykM6Lqum6KqkI7lrML8QHI2BJFUbAcAIOAsEA3QQIMMC8PWs6ligvIYD1GBnNQ-iTU1ZjUHh-0oOcGY0CWa9wgiVAlnsVwBByNCjQY9wCHQXH8cgwtUGdSUlhR7ihW6emSI9FF-dZ6jSOB8NcDOOAxfwuUmfQHcUUkRWPjgWWWN61ju3MQiaD2oTqwiwTcoILIllPWgBDgJYWDc6JEsxgTeYiLpJvWE2EseghYLyHVnWdkcbgyOBPeodioKgSgOwKPVZiWKgiKDlEsnWR52Gj2OZ3jmZAHozQBQxUAC4TAAXjQBs-UAELdAA3lQB5xPYQAseUARbdABoVQAgfUAU2szQGivAHdYwBpzUAGJVAGW_QB9c3TyR2EAfvlADi5QBXDMAI3TAD4zQAuTyz1SUkANqdAEADcf2EAAHTAEDIwACeUAU0VAE_tQAUvS76gK-2GBEkAHgVd8AB1NABG_AvAHvlQBTuUATMVACxNQAnhmABgVQAb6Ytz_oAKjlr7-mrBnQArg6AHxDQApuaAAS7ae6BWCuHDqgdia8CixTwTnWYOAkQRgANrtH8IkVikVOztAALpvgsBnIB7cK4qWwexFSsU9rcEyvbZSBBErxSavMCSKkhryBWrAieHd2Gdk4VMKhNDOw8PSnw44thBGJRIf1agIieJiIVnAah5BaEszSusLoBA-jmCjCdWscAti6x6HBDWAA5b0BV-AjEyDMcQK4EzU1poEmswxWAZFcB4WAoSLB5VgIVKCOJ0ATCuAAJnUtJeQ817oWDurkxgRM0C5WJpzAohVCEpHKrEmsMYODkDcXATxR4-A6DwMhbYASHG5NqTlBp_gmleNaYlDp4gRZdEALRyFck40EAA3OgAKdTzoJGZBB5CLVjDU-6jAyY3GZNQewUBYjbEKipcqKkpA7IpvknpCZLl7NQAco5JyTEqPQGcl55AQidgueTag1yCk7lkJs2sfTGnNO8W00ZIB_k9MYA7VAYhABYCYAcfirah0AIXRgB070AF-KSDpk-z9nAAOMAllVQJVAf2zo1mSHhTC2pgKqzAuAMYIw7BADkBoAX4DAAY8oANGUK6AC0FQAqzbsFZTyHJNyLDABRmjZy9iJX3V4ZlOV8qtmgoGeC4Z7TUCdOhcC2FtLABXyoASHMVlVx5fXQAs4mAAbTQATHaAGXzZZPtqW0r1fSparqOSKRVRYMhKz6HAo5OK2FkgikItCVIYJ6B8lSCcXosN1Y3zzTMBnQAo_qAG8MwAFK66C5gQQAoMqAE-I9gxd66AF35QAg9GOD0FgAgFdoJCFYFwDI8yC36xrYANE1ABCOqtLA8tMBbVCDtDmOaIytrfP9QGMAaVuz_ODKGwF9CGAnuMv8mhbTrTQC8PmXQsakWFgIcgfozAmxGK4ccAkIwxCPFcVAoQiCBAEOozKzoxh1siNSCMXQ4CUCgBE_aTiCCWH5DAfYH74DfoiUsS9MAOQfhYmte0Xsh3OJ5k4rdAtYoboIGh-Ku9AC4Sn3be6xTwNDxlh82tAriKrikbe6KyriSS9TWI8GtXBpAkgACXnNHTy91o4pHuOwMh9CQiElRAUITjGpGMZYe3QAsyaAB15dg6bAAr8YAPbVU60AroAd6NAC4sU_asRBwjRAGgJmjNYM6AFFbXTGnGQOJUhGGz6UzP3WsbY9gOoPDqGcSQjWEAUioHikoM2FtTZ0eC7QD4Pt2AAGogppRVA4pQK5ErRAc-RmATl4AVDY2bacKQnOhNcwIcwznck4HKzZxjPTmPZYy6E-QCX7pJYcXxtLIXlK8RjugK4KI8sFe6ROUcbnSv3XKzgSrNS-MCbIR1teSwxs2ZwFN-hVWawNZXM1iwm32AZw7gpwAMP-AFOjI1AjHZ4AroAcyNAAyEYAYXN1jQNMyuZRnznvmJEadoReAliVeDtbOwQXZtdf6z01wJ7xxg4IBGQKkkYewYGx94RUFDEBUoXQ-LoSM5qYrg_eugBqiILRXfsb6nGDa_T-2zPSivmFRAyRra3JPSYnliwAejpsvYIATgtAB2xjZwAM4nY8ABVKW8sW73mQvCZXObMP2rjywAMAGC7NUR0cf2rilZWYS4lbXaDA9yVT8Lf4VkxaCnTrbDOzBSIS-OrA3oKKSzUOrLQFB52gRADgMAsAfBmYEXBcyhp3cJaTagJbm1ILrC9_BK4bvsB08j1gS0EAuhxumt-29qA6ebD84QK4AB2SSrA8DR_d-aDw_hbBXAAKySQAKR05EmJc0_QUiZ6CmkuS_uWJB7AFMSLyczMx6LyXgTSZ1J55rzTOvXhdlEHSXns37fYCd_YN3zMOBfEJzMB3rvBuSE0HNHEWmgl1jp8bwQK40Oq-j9EmgIvIgIhT-b632fMCN-L5SyHryhfi_5EH8P_Pj_1_z830mEX1XxmEUmfw-CAO3wtD3xyDMyPybzP2rz_0Dwb0IAP3f2wFPzTxgAzxP3YBzxH2QNDFwOAL8TANQK7yZl3xplgMPxwOP1PxhyQLmnbwoMXyoJgPQIsEoAiQEDd1GHNB9zCAiDbyf04LM1ryvwn1vyuCTF0iwFEP_0oFnAkLHykJv0iFPxbwUMf3-jgH3Vt1BnBkhm0GhgXThmojOh3VNEVASzZl8EoFsDI2MzOB3WGCcPNFQGoHr01HNA01iFljsOoiJxmEsGangB3RCPNGR0CLXQJnBG4gtnBAAHEldQ5GMGgAAZLrOONfasBoHUGYXWW2cEStHNDIrULRM7SKUSEo_Iyoh2T7DIytWgatDIvUIzYogANXiAZFCh3Q9BiMPVYgjnZhRjFHQFCARAEBQxgBaOwAIFmJlWHDQgEjOgSwzlNCwCuEAGwlQAL71AAAOQgUAHgdQAaPUK5l1u1e1NptopQ-oXFMM0NGB1hEjaBoRq06B0A7BgBtsOjyMTMABpe-d4kCL43LAoRjVIkOG2IE5IBYz40TPLRjSKONOAWEkEhE8EmcMwBsSgIokzYaK4Ao_EiOQpdLJYP44zCOWEpYKEv7Gk9gFE4o2EsQbXL2FcYjUjV47oc9R9XIcMSAZ0RYFcQzf4iOWKH4kTGiLYIUdARgSk5k--JYBoBUkzHoilGAMkrIMQMQVbB7K4TIEYJkkzCU7bCwNFG2A00HOk0OU0qUhoG0m2LU2gWktImE--XU9YVYvYEknBdgAAH39MEnWxgQsCIAgCwDQFVIjgjFFKpJwTh0UUG0mOmJ4RsyuG5KWGgSJIaO0TwBqP43lM6MBI9L5NV1GNNmR1JHgjXiDmgWNw5nYjjJcQEmbJMzIWR3oQbIzgzWnkAAB9QAReVAB650AGCNEBEuZeQAH6Mf5ABMVMAHvo_bb-QAWDl0UT5ABpW0AFXowAMMjAA1t0AHDnFcYwTgVOVADwAdTgfzfmTgZoHkFcCAZoC9cI1qdgNs5kAAQm6naG1jjVfC4NyWRzPAECEAEEfPmDhyaxXF11fICx-0Bw7HAtQjpxNnmOrVijenIFNFoBQoIGeIcQaGwsYAaG5OdIy0ZNRJZIpOLOpKVPYEdLRJouNOosSAqgbBvQXCQorNfQbSnB4RCIEyRDLOSwrISiSlmiDj4obJNjIR-wRzwGExdicQmKmMjC6BTIyB5gFLQGyEtlHBI03XSwQuYSZ0xQOMACijT4GYcIbIISnBKYCyqy9AOi2KaVdGSgCMKjTDR0ki106E-i5ij9a0t022PXT1QTUJCbBxRhbsieQASuiHtrtABZJUADgVQATlMq5KkbKuFUTnKnFlj3K-TMNGKcEAqjTUS7YqjPtQqhNDLGyphZKiqcrUZXLlUFUCrSNczqi40IxZKsyyq9dE5HQHK6LPVQkyEHsBqBQhqgrvt0sVsVwaqM56q41ABCm0FX5UAAYlQAVH0M0S1ABAYyXKrnYEAAEjL-QAIGN1rZLAAoOUPInkAE34wAdgsM1AB2I0AH95DNFGM8MAMAZxUy2S-TQALjlABc-UADVY9gQARh0lz65AACBMAAQjQAA9NAAFNMABezK1TKqYfMPADqz7IqnMiqxKIssUnBBkoqvyzKASWSqUpEBsjGrGxKIq3RFszRRoumuNBLG69gC1QAXxVABYFRs3YF7MAC59QANiVAAK40AFrTQAbx9Ti0bkzlKBBuSiTiLMyyLFTmLYo-b_AQhUBEhor2BABNv0qVzkAAKlS6g4wAbLlZgIAZa6A_BWJshuTYobb3AERsjs4Uhc5vKVaSz_LVK5buSardcWralsKcAuLG0BAalMENTElgV0zoRUBbaER0BMzgVZKrgaa8b8y41VsekLSBN7LaBHLpqalslQk6UqpRKwwg16UQ6w6px8kEKQz1g66MgcAQiwj5g7ABJVdK66Arh27wi4AcB5x0ZjxbAxQABHUIePbSr0tm42Cs7NZxIkso5xWKNcagF0DRcs2y9gAAdT8T3txBbqOHPT4quAEv2iDoTBPrpUYA3ozEgjGz4rQmegGykFvvmvnvugzhNrD0SHBXZt00AE10reVnSCobYrEIUSW9fQFo09Z0ECg-2YI-iYE-qmR0TemgRu7-2q3IVDadNRfaFGbCkdedatBC3Xb2TmZDfBmANDRC5A5C9jSKdxZIs8AAfUyJ1Eii1AaDYdinaAAAEhGUsZwYBXb15uS_Rf6T40rKlt4K5ABO00FTMG_JcXDMjNQGjOKugrgG111w8y80lB83yH81jKopwSRhXDIXsBYbYc4e4d4f4eSPoU11IrgvQG11o2EpCDjQZNsEkfwW9M8YbIsEQ0NtmBTnSzCbwefSCaIRmAlJqTG0idAJqTGv8fvnoSuECZyJnBzsYYGyoZSYq3JMm3yfdtmGvRgBGASaqfSYGxDKa0kzhwDwzhRUAABzK1C49LUyi0rFXFNR-4gaTgUjLy5Wui9E-EjAREgoBsKxQK3yokuiz2qZj0xjOOlO98P8k2POu0zWh0oKtZoKlkji3ew0x02Eru9zTzbzO-RIOAEq4aunS5t06513IUM8CUPAZ5954E7emWt56E2KYFv7MhMFjIWErsoF-AAdKYASSFkCxzBsvOiFpZ62aF0FuFnaOnc3KxSB8wPOxQ1ADOQAaqVdrAB85SNXYTjRUfYHmWgUSqSqRpSoUZ3lQHUdGcKtRJKpJvKuZrwDZMXttrSHqZ4WgQCbgHqcfRXFkpwwG2RyuDsdYfYa4Z4b4bYcKc8ZVxqTybdsKYsElaVPq1W3xYXt3oe1NPOfYkNKKuudiiMfufviebtdRModB3tZdc-YEG-eETdd1nRMBbs1RPRdKsDeydimezijG2gQLP0f2hGw2HcDFcqb5aycSEgzRGg1iZNk_XhajYhwnE9dDYDYGmhYjFjbjXjZqvCdFYkbTbLeoAZK2igCcVrcGxsSgfzdxY2zxcTOrF1wexJcWszr8eKL5xUeTQnjUyJ3DKcWGZ1m5faszqKu6szt6uKJmpCzzoMcJeo1EQ8ZrK6zSgyhXGfXsmJxgHcs_XA1Iq6GMYIEEoG3vLimR2fIew7PCPks_K5eoF_KTaNdDc7Pew3f6t-1tIB2PfgrBk2AC1vfJw7bQnnBJ0A8PcAuAtApzfqw2zaa1hGeZCdrtvqY9uVpJumY-NmaxJSAWerDjqIvS09vI42bMA5EVsY7I_Io9L_Nffce8evLijfMWzSf47iXSyW0qdzgObsCRA2xlqjkk6ie9JswbLSe32SAEgea7syiTa09ihqMyESBwFsAM_ggjC0_SlGj06uDIS05heKf3a0_G0iGvYef2jfLfLSds-yZquaZrF1xU69OQL_ep3CIw6FCw-1wmCEDGAvfC5AuKAyGAvHdGaVYABJgBkd5Big8OYEQvg5czvdwUIxkG5hsgH79A92u3zBSv5h0AKuaAcB_7wUAygzavyvMHH6cBUAvFWu5R2v6vOv9A_QA98ux9YGaB4GtKQKpvEGlgBuGun2_yM49bX5AA3uUADyNI25rrxdYWb1unbo8WKYobDDL08Qr-CYrhbob7BqRYoOnDOLb_HfNdbiubavb4CBB1u679MLe_evxOrxb15_d9wlEV4gIshmgL7mY_72YQHm7p90QvQgwmIww3AWdUwp3WGcCeWVlTWqwhmbGJiee-DMcPQPgiUboFUyxuAdUhkEinT9YdxjM8kvsaD7rLWxINj9gbkkl0nsZ62LaGyI53yunzU7kxn6sZnnngyi-7WvnntdaZHRoJW1n5juEyjsE3reZ2KB2xqMzNJokkj2YRgdXsQf3EnxX-0ZXrIxTmYU3rjjX0EuZmcMm6UqgWmB31WpYLwb9cUBFdgfX-7bWyjLTq4dXgMr2pi6q_aX38kVPM3eetAQXynxoIqgil48p6sHRvyyE054EmZrXsTAp9YCPwvl3woAPszLZmJxXZZ932UxgdZzNxoOisXhn830PTO3GwVgm-M3PqPomj0hX-WG3kmwo3WXCiwbZiwHPij536jvP3y-fzE7Xkv6sMvzXivnEtCmUz30vx3xjIOsw6tIk9P8hnC5Wufmi5vpYM31bHVznxjPk19edzUpEKsTvmBfn5PjIIXqnifiZin4y8ki2fGnivyo5r8UgS_ekgXy36L8D-qtDEpAOL6V9Je5pXSlyXSyUY-SvPYSDTyuBigPecpa_i32p6E1aevRcXulh1J0486KzY5pM3z7MU6c-pNPqiUAHUBPapAnyrAJb5m8R-66cnv_0aCr06IZmMhr9xoCEDNQOARbh3Xf7a1WSMgs0BdHsCCD7Qv_CnsL1pr5laiffVWugMPZXBSBdOTxoSU54aCyeKfHQau2rb6Czef5JVoPz8pmD2eFgqmsgX5429dBBg72nf0d5u83ovghoLoPjZ-CmKOpQPn-WzJxQx8AmUIXYN1jhCzeL_GUNSEYAf8LecGK3lMBt6rMmBy_OAQvygFBC9-cpW_q3yCrt8JeX_K4kIJsFU8M-1YGfuwB4HrBm-yAovkiUQHe0uh2_Kvl30FY988yEQofvwMCF056OqvELAILr7K4qhvlE5kUJYG6ElAIAdyNsFYwrov0qAOdHoGdxmZ-APXI8PwCuD8AfgxYaoBkHNATpHI6wfgLwXyDUAzh7AfgJJBwAfDJI9w6sPwHKAYUIAvpV4fwEii5Q8IsAd4tbghGYAyQlARILmDyD4IbC0BIsH8FLA_CLA_AKIGgGBEqApY_4MOCAEYx_CYA8wcMKxAgDwBXhpWfgMiNxEAA9QKNpBwCBRAonwjETWH4CKwGRSYHAGkjLzsiiRK4fgKDwtBeFUAPhM0H4X-JnAeROAXkd8KFEOJ-AURGIgyMCgCis8fIjkZiMEC-ErQ6o5kayMFFBc5oehNEdLGoCQAUgBI6gPsJhgMAhIIAWgD6DThIALhmFGAJKMlDmg4e_AeQAoGyRAA",mdxType:"Playground"},Object(c.b)(g,{mdxType:"Count"})))}m&&m===Object(m)&&Object.isExtensible(m)&&Object.defineProperty(m,"__filemeta",{enumerable:!0,configurable:!0,value:{name:"MDXContent",filename:"example/index.mdx"}}),m.isMDXComponent=!0},"./example/store.ts":function(e,n,t){"use strict";var a=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/regenerator/index.js"),o=t.n(a),r=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/asyncToGenerator.js"),A=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/toConsumableArray.js"),c=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/defineProperty.js"),s=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/slicedToArray.js"),i=t("react"),u=t.n(i),d=t("./node_modules/_hoist-non-react-statics@3.3.0@hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js"),l=t.n(d);function g(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function b(e){for(var n=1;n {};\n\nfunction Count() {\n const {\n state: { count },\n dispatch,\n } = useStore();\n // \u540c\u6b65\u7684add\n const add = useCallback(() => dispatch({ type: 'add' }), []);\n\n addLogHack('\u8ba1\u6570\u5668\u7ec4\u4ef6\u91cd\u65b0\u6e32\u67d3\ud83d\ude80');\n\n return (\n \n

\u8ba1\u6570\u5668

\n
\n
store\u4e2d\u7684count\u73b0\u5728\u662f {count}
\n \n
\n \n );\n}\n\nfunction Chat() {\n const {\n state: { message },\n dispatch,\n } = useStore();\n const [value, setValue] = useState('');\n\n addLogHack('\u804a\u5929\u5ba4\u7ec4\u4ef6\u91cd\u65b0\u6e32\u67d3\ud83d\udc90');\n\n const onChange = (e: React.ChangeEvent) => {\n dispatch({\n type: 'chat',\n payload: e.target.value,\n });\n };\n\n return (\n \n

\u804a\u5929\u5ba4

\n \u5f53\u524d\u6d88\u606f\u662f: {message}\n \n
\n );\n}\n\nfunction Logger() {\n const [logs, setLogs] = useState([]);\n addLogHack = (log: string) => setLogs(prevLogs => [log, ...prevLogs]);\n return (\n \n

\u63a7\u5236\u53f0

\n
\n {logs.map((log, idx) => (\n

\n {log}\n

\n ))}\n
\n
\n );\n}\n\nexport default connect(() => {\n return (\n
\n
\n
\n \n
\n
\n \n
\n
\n
\n \n
\n
\n );\n});\n")),Object(c.b)("h3",{id:"\u6548\u679c"},"\u6548\u679c"),Object(c.b)(s.c,{__position:0,__code:"",__scope:{props:this?this.props:t,Playground:s.c,Example:j},__codesandbox:"N4IgZglgNgpgziAXKCA7AJjAHgOgBYAuAtlEqAMYD2qBMNSIAPOhAG4AEE6AvADogAnSpQL8AfIwD0LVmJABfADQg0mXACsEyEFRp0CDSQCojvVO3YAVPBDjsAwpUwBlAIYYARpSzs8rux4wdOyuAK4ElESuBBDkrlBQAJ7sAOZ0MALRMOjsoXBoKWYWAAZUmHDu6F5YGcU47ACSYOyJlKEA5AIw7OShAhBtdniUAO7sBH4Evq4ADjPp6IotbT3uRT14MOQA1uxtU20C7OiU5EMZMIi-BAQzcIiSkikQE6EeOFREkmXwldXfTl-nm8GTM6yay1C7Gw836dHI3XcyTgBAEoRSKVgdhGLzw4xsdgqwKwSxmsH83UytkR5lscFCMHW1FSuLe9UQAEozEZJGYIEQZpQBFMAEowVzkKZgIREdidcWS9oAbj5AqFooVBAAIgB5ACy7GlkTlXQlBAAtCciMrVYLhQ5IoLUPpDTK5ThJNhXALYDbUGYxWbdXqcF0MBkABTrZhsMTrCyMPAAZjEjjVzponDVwuy7BxEx6gKJVRBAkQUmTcfMFnYjBmYgjDSmUWSztzrnQLBi1Hi7BmQju40oqRgUxe7DDmH6qBSeZgCQ5Unr8draadLskVYT0ljijMJ16RH0ODSBAAorAjzQAEKJBroCOdYQEdpc1AcpUKZRen0wHCaMgdGoWh6EQFRsymYB2AABSgVxEhSIRQgwdh5FdY12gPAAvP1-TtKYzywb0yW6I1ZXaD0fxIyRCOI30VVQPD1XYQNJXQ8jTUVMFUF0FFjlOLDnAIRIsXYbh2GAdYogEZ5UCudoAAZ2CTGYsHaPdqxmDsWBneSABZVPYAA2VT1PWMBgIAMW9aBEnk_hnEOBEHUwGChH4JYiGoSg4C0hEzOrCyaGcCAsMudgAEY9I0-RuN4qYAEE5jE9gIyg8gbCgdAw1QjkxLEVLoxkdgUREmBuGAbChLKuB5C3GtgAy6BsuCAAyVrxkSeZKGaJqspy7hBrlMBkMlAZUHadgAH4NmasMIzyq4-pa1BYurKQZCrN8zBhZjMDAMIoCmBb8sK9akuXasE1YggcEszIUivAh6prWtaN_dhNxXKQbruh6nvqqQLq2swvxAKjYBoojfxwAg4CwQDdBAgwwKY-0bqWKC8hgISsiWbH7HiKAPAlXY0LIk1NVwiCJIcVwBEWdhr3CCJUCWBpUBmcJUPYuV3AIdBqfw2ndGdSV8bgHGIi6HmKYoyQUSFGAheY-XVGwD44DgFX7Xafn0B3FFJH1zXtYYsxYCmbSABlKBSAAJUmUojVh4iuFFpxSPLuAK4B5HN1ARp47tzEcZCCBOyTq3iiSVxRLIrnStpMyUFcWF86IMo0iw0PE7GhKVhaGIsR52EAGBVAFNrQAQt209YY-0lKCaJkmdgjE6feOWwtIIDK0s6-Z5O0yb5A5JYAG0AF0P24iwbbtx3W_aQBC6MAB1NAAs1QARv0AN7lAFnEwAG00AJjtAGXzQBeDcAAL3XwDiwugIPpzCjK7V3pnJhlYDJXA8WASuE2AKqg6TZLXhEBEIgVwABMelUJ1RXAmPAEUxCrzXhWeBMDazFXIHBLWAA5b05V-AZWQtscQqCEzoMwXAHBR4-A6DwIQ8QisuiAFo5KuVBw6AAbnQAFOqAHozCSrCaDyA2rGEhtZmY3GZNQewUBYjbAqtpOq2kpCiNZi9GsgjZDfUkITBm9Vp6rW4kHMazJ7CTEjnXagfEo6vXjrQRO7Ajxa1cGkVC2cazp27lndYudciSwLl0IuZjUB8THq7KADIliSwIAANXiAyCejcfEECyI-S-M8Qidltg7Umj5ABSAYASk1AAl2tvfex8T6AASdlJ_po7mIOKgYx7gnHiQjOFX6dSZwwDPG_GgjB7aWD1NbDmXNzyXn0GIb2vs05d0zngNKqDhIDzlBlaIAVXoWC0iJSgHYrh_kSTJUcOAQkMhXCPTxqSb53zOq9RgWiX6UDfpkT-3RSq_2AP_emgDgGRHAZA-Q0DH6wPgXk_JyCVEWEAMr6gBZJUABG2gB7Ay4YnexFQ0hrRWaogZ3MJF-DaRVDF9SYBoTJBKGAwwsoZGoYAd-jADJ8YAU0UYX8E-ioqQ1ydEMTWmYAxId2AZLSAIUxVTAlTDHlAO2cBwmjgyXAOJecElZEYB7Aok8GyT10bPdJ88naNKFSkd2qIChjJKmK4VEZ-wwFYOK06gq7ZLBwNa41prhVT2LhOUc5yH6XOub4W578HkgtrHAsQgBy40AGxKgAH5WBagmMHAMH-Aobg6hmq4DEL-Q1eNOAogzDbpqpYXAsB6tdSi1RMwejkMoXgkAmq6XbBgIkCq2bfn5pWcATVyL621kkJdetHIOTNsuTudRj9GXP2ZWYVlzosDC32odKYostgR3buM6sZyBD32-mQ6NJbqFgFgFgRNKKI1FrXbG_gsAwCiBAD60hbB93YMPUBcOO6W1XOTlML6SbtybWEXuqN16qH4MmPe-tVzJj0o_b2n1aiwOru_aW_oKRCD_pWYwLlGRgNJvAxo991ZdEjwYmDCGMAFbS22VoCgwF9CGEkN_JW_4E2MRpmgF4vjSJugoh6OAAhyB-jMDHEYrhxyNJiEeK4qBQhEECAIPVzoxjQRlNSCMXQ4CUCgG_U6ETLD8hgPsOT8BFNvyWAJmAnaA67XtDHJOqAxYEAllLJWPNxL0YIIxmZ1ZS6AFwlQA05qADanQAgAbrFPBzBjiSbGpVzZYmsfCCBXAUi4iwCLHHhXaPbecQrlk1njVcSeIRCQ6pnJPFxI8XGl0roAWZNAA68uwQAo_qAG8MwAK_GAD21axMAq6AHejQAuLGAB4FKS4RojjXuLHR-pdACitq1hrK5tIRga3lULr1F3mB1B4dQM6cDRogCkVAaUlDfzxiLJ92qsgfCfewAA1JFXKjqc7RY2NEcbgWYCeXgIi8KcqZyTdQTNvr9brU4Aaxd16sW0g_dQmd5xK5NXXa22soVmzv6exe0mt7U2Vmfe-8ItL7Ax4Q42YzJHN2cDxongD_2Ry8sFYo4AIH0K6lcADD_gBTo0AJDmbiplV0AOZGgAZCMAMLm6wzQ9cTqNuAiQeKZf5-QBKnY-4M57ngcJN2linloAIOwG2Mcdlhyi1wPHxzq4jhFBSOvlUrPF73KCcz4tD1O6g0udWq5tcAFjygBqiMAKDKgBPiKrv2SI1InUKaU4yOHzql3jDRDAIHqdqzB5LhRpegB070AHo6gByA3YIATgtAB2xg1wAM4lW8ABVKXnI_rFLhwwArhmACN0xhSeGttaroADeVABoyoAGADM9V986OOXvWEfhdgnkHUzowe0BVyst7DX9vhyO5FIPxPh26Nw9Dai6tcBnGI0BPQoEQA4E3dgd7ndfJwTsoaLdLLuK45gCetfbjN9XBX1gR1Z_LQQC6IYuShZQlEFQI6zYK3CBXAAOwKVYHgC_W7zQk3yF6wAFYFIABSR1ABNAc0GDN_SKMBVSXfSpffQ_AfTMULS_AA2wK4JMPSb_CAt5KArwMRUBdgMBb_YdPfY9KYVA6gkYTIGYDSZA6g8LdgJbGgc0OIBmNfF_WDCLSKHXcAqSAg1Af_D5EgiKeA8_CgpAqg1gxZSCcyP_TA3rHAvA6QswJguQoDHAOg2YRg2Qj4bQ_mDg5-bgmAV_Pg7XMAxAjQmAhQwKLdK4CKZ_cw3gj_L_H_dQ1AUMCw1g3Qhg2w3wtgi0TgnIULHg2AqwwQvRJAuw1g4w0ItfT1AQTdUYc0LfMICIGw7wxI0LSAkQogkBbAkyKQmIjQzVNffI0Q4gpwyQxAsGNjcgSQafWGeGRGUjRfNGDUM0XmeUM0HWKYYYWwBzQLWIOwOWIYlEc0VAagaAzUc0axMYgYvsAcSwLqeAXo13GYc0Y3M2W0ZiULBoXGWgFxBoAAcUbwyBFXBFtg7AKD1D0PBB1BmBDmuOrAaEcHMxnVOK1EmQlwShkjeIsAaF-Izgl1OM-NoCwEs3BD1C61eOiVCVu2HV6I9F2M4x4mqVpmxjFHQFCARAECs0hOwEs28RgD1BgC8lsxYk1AYlLlNCwCuEAGwlQAL71AAAOUACo5QAeB1ABo9SrgYW92MymAnVCCOkNFGg5Xs0Y0YHWGOO6BJLoHQDsD9hcThNGPMQAGkq1oRoTFSstPYXELibgritTkgFSMB9SCgXEEpb84BTSdSQIlTocCgzAGxKAXiesporgGhnjXjGA5Slg1TutNSq0lgjSm9TSlgbTXjTTRk19TMVw_NGIRishnY9VkJ9o0BsgLsiB4SesUo_ZMsaSqAGZGAgyYzQzGhyyetESGR_SbsxAxALsudzErhMgRhoz8zxIVTEzLj5c2y1dwyriCy0J_BGghz5d6ytsJy7Sq0mzPEUp3TXj2AAAfFciSQnSpGLCALANAas8xCMXM9UwJXRAJPiLoPEgk52BrK4AMkIW_b00E9xPAAElIOAMsvMkMxIOMjuFvLEo3dY0keCSHHILxFsp_FcGOI84M8wcSaC14seY3CeIHUuQACldAA4uUAAB9QAReVAB650AGCNQAN9NAAF40AC5PQAH6NABMxUAExUwAe-jKdAB75UAFO5QAWDkl4qVABpW0AFXowAMMjAA1t0AHDnFcYwTgeOVADwUUzgVbGzCAZoHkFcWS1KY3HqOxT88wAAQiGnaHZXGlfDX1emNzPAECEB5WNz1xzhXDe3gvGi71uz7GAsxz103IsBjmJOhJSl-nIFNFoDcoIBlMfg-NI2hMYCOJuynJOPYE7K_KbPYH3MCUjPYBnISqivirnPWAbGEwXEdRjmk1uS4GQ0aS2N6yRD1T_P5RFkyhWmpKKqBxjjHm-3X2fMlTJNxPxMjAvLasJNyHDEgGdEZiTICyyHMs-nDwjzZMACijT4QZbISCrEqa8IbIGc-JckykygNuPVezCc8KuypKucuTQcvskVTbbvceVBZHR-B1YSijQASujwK4BWdwVAA4FUAE5TcvECl0x-euW05aikryda06ezFKuAfajs20pYA3PAUeNHS6z6rEiGlKn61avNV6OdTgZMp8qZFKiMCGpYO6qXLbea2gdAGc0eVBMePGwsAUBa4mw6_G2gfHFcYa0ueG2_QAQptABVm0AC0FQABiVABUfVQsAF35QAQGMWLy92BAABIyYsACBjbmiGwAKDkrr2BABN-MAHYLVCwAdiNAB_eVQuxjPDADABnXGohpK0AC45QAXPlAA1WPYEAEYdFi63QAAgTAAEI0AAPTQABTTAAXsz3lmvKvzDwAxv-IfMaH9oyg_OPNnMSCjNtNjJSghqLKRCB19uDpfNvyW1vxjr-IyhSqvhrFLh3kAF8VQAWBUGt2AMLAAufUDUAArjQAWtNABvHx5O9vPOyE6rlO9LlO2sjorO_JSmLrHPjsVsAE2_d6mcB4mYQAAqU5a2TABsuVmAgAbqmDoExQRHQDlJSgXvcARFuJ0hSBHvbsiqjr2o6oJLlOGre2RtUV8pwFytYHyoEGEQOXKheWEQsBvOhFQEXuyADKfsaqmSuETozuTpDgB1ellyuKWkdGppnKAagVQXPV4UqrDG7VUU0SCtuivpvpUScuzrQanBwC2LWPmDsG7MgvgboCuDwfWLgBwHnEpOPFsDFAAEdQhr9szPFs6Y5p1JRvTPiLMUo1xqAXRfy572AAB1eg4R3EbBgq1KIqq4Eq06M-hMSRgQWBx9dMF0YAT7IqtCF9XdSQJRjB7OsPdgce4_eCEtRW1rQATXSvNY9LLfdzA3kRN9BITeNnQeVRHZhxGJglGlg-GMwCBMHUkY585CM0zToCYUGIxfK9d4dCwvjxYyTGNAcvDXL7YEosEzizwAB9a2HUBKLUBoDJlKdoAAARKcWTaU3oKDlL9FLlHqpVeqHpSG8yrkAE7TdmtlCU8aOxHcvctS4G6y8xHvN7ObBbSUJbLWFbNbQZwJTGFcMeewNJjJ7J3J_Jwps4ieWyoC9ZZXfSsLf8-8kOBK2wKpmcaqhyjsIHFyg5ppke6khrK5uJviSTU57e2YAs4RT7W5x419NHcC00ieK4E5zHK01BZy3vexvZ16bHPGFHEF4e2YITGAEYV5kegnEfTxLDbiUuQALATABx-MAABzPefkm7cakBgQSPQAL8VABTc06eDm6c2r7N3t2rNN1ItOdJnAbHWHbJnO9JnOZcOtjJcRfs_vfHjKxPJcIY3KLPOMOoFeNPl1jOyqxJ5b7NNKldGcW0rUSGBtVYVbgBiYOoVfVeXyFDPAlGmT1bl3tMEdhvKqtakYdflzHidYBdqpVfgFFKmHEiddsuGslZdaNetarWavky9cdWw25chcleyNLkAGqlIWwAfOVaca5b92n2AOFwKHrHr3bnqmnvM6W07AbbSQagbwb_6e92HMU0hXnnZ_nKzgW7jnt8oJkwTDdZl1irhFn0nMmcm8mCmMmAcld0AedfnG2t6oH62I6wWLtI2-U-I7qRzlX7W1cUr1WUpNXxntXdXV3bTDXQajmq1KGLIBBzXe52y13tTbXXo7rA2D3xoAWUp_AhdUpPtwLXyDXToEcXLq2YBXnS3b8ErUQGRhrrn7XPWxTxI1deMpgL3bS73L3EhNm33b8P3QPLtKn4WUgAPD2I7DR4hJZ0O3sw2jog8I3TyF1IW7rY2KNY67q092mzALdatXciBqRC3JT0b_6sacbDmes6a7LJXhnIW-5jdtmQLcoW3H5JM3I3dJY255MdM7KugxmAmv3UFFKIxlLmhb2kKxItKdLqA9Lv2axdP1jNmoJeOGrJXnF7Kdn0AORYZNg1tFOvd0O0J5xJYoWaxDLjKhQtP1j3OjksXKlDPzA16eI_2sOd67ygb7TzSnSnsUguXqwX7QqZVYv97vyNIORW6wrMvO750LBNOJs9nNP1L-9vmZge8rEccquRy47UBEgjkhGoJ6uvEHmQdovZg2DkhxJt31Ok0BvxIATMhEgcBbBRv4IIxt28oZoBv0tt3kLXtIXt2vtIgYAZur2Cp1L1Kqux5tWYbXpwXr5IXOuQ8vCwv-52k_PTLAu18JghAxgZOjKTKIxigMgTK-PmRjdEAAASYAY3eQYoELjj7p08J8zfEtCMDxuYbIPx_QYT2-P3WH-YdABHmgHAUxxIEtVc9c1H-Hx0fhzH1AXBPHuUAn9Hon_xv0EdK7xxp6FxrMnlJntxpYSnjHtT0LUufu7eQAPI1R7sfzHqxWeMgseu4oeyfxJihHMAeIeJezHcEYf6C0fOeu0ORihHVS4BeHdHdt4q5-b1hReBAcAOfqeBGRGVfCe1GaBHU3tJiUyYgzgongJXGMh2ereqebeAn6ilAQBGjJBdjmjwxcA4Z2iF8UZwJhYjAiybpejOIXwjMx1mI0A5cDpnIGg4q4BayYBtqivUoRXpc7OQLZGmvcv2A5TsihS0a0_CVxzDqc-8-19bLbybty--6yjR1hZjdGg26Cuet4u2XEvstkuUoV6OpQsqvvTUXZhGA4u5yWUk_u_1jGgZ-Zg5-suHS9SOXR_xIxQSz0AN_O6lgvBFNxRUACoJ_OcmurgtudWrh5_kh1zH-lVTpT_yQILVoA5U-Mh0_ugGgUqvlfyhYFFYWAs-kZdYCyy37ssku1pTfgl0tIzhXSa-QvlkEdSSsrg-_IUIfxZbswZyjfOUo2UdQQ1Hy_9UOjBXDod0B-C_LwtXx74ADbSvpcaMAIr7S51g4A7UggJ36GlBWnAofogJSBwDO60A4fp7GQF78tg2AlgY_xcRn0kYJJb0oAJQa70OBeHXAXvUK4XZh2pfadphlOi5U2OksRgEiCrBiAl-KfPQCkTr4-kPS5iFgaANir9NB-jpAQTwP1bODt-sA9YI_xEECDkBoWAao73Cio1K-nWMOpgMkGllVB7MLPgQIbJmCG8-rPlnKzvIssEh1YO6ooMYG2DUAKgpwZWXUGP90hI6avj_ysEZ9uGM6ZvpzwiFmgcAnPfBrnxMHl9folkewFX2T72gyhf_IOtx0BLkDCuD3Tto4LDqmlHUw7L0iECa4dDhY3Q6wUnRSofsj-1AruqFl-4aCVh4wi5ugEmEd8ShnQqYPQKToDCVhVA6Kh5U1A4BjhIJPoW-ROHRVL-a-cCrfzeS9YbhbbABuNCWFFC9UBg6kMYKa7FDzB9oegfy1SG8DWWLgnfgVAkEH9GA6g2VvqziFZAiB3EUoZYJ6GBU9AwVWUmwOrDRDIBEI3wdwO8HwD-BO_fwesBIG9CPh9w1KnhyKGOo0uffIvoyMSFN5kh-reVhGRoExEwYfkbYHFmozUBw-yMBgKFn4Ck8jw_AK4PgiLB_BSw5oPDB5HWD8A7k-QagDKPYD8AFIOAXUQpBVHVh-A5QLyhAByFaj-ACUEqHhC_h4ZjgMAMkJQESC5g8gBQJ1GaA4LyjiQoIEAC4n4BRA0AFolQCH2oyGiLAxoh0YqXhAQB4AWohHPwAT5BiAAehFCMg4AIoEUPUWGJrD8B9YyYpMDgDARAEsxvolcPwAd7TFZiCfBYqMTOD5icABYg0aWMfj8AtiOxChsmIijFj38hY7MeGMEDzErQnYtMRmJLEnJVoDRBUTPmoCQAUgwo1AKKLIxgRJIIAWgD6CyAyj8E3lGAHMQ9GzAZg_AeQAoB-RAA",mdxType:"Playground"},Object(c.b)(j,{mdxType:"Example"})),Object(c.b)("h3",{id:"\u4f18\u5316\u65b9\u6848"},"\u4f18\u5316\u65b9\u6848"),Object(c.b)("pre",null,Object(c.b)("code",Object(a.a)({parentName:"pre"},{className:"language-tsx"}),"function Chat() {\n const {\n state: { message },\n dispatch,\n } = useStore();\n\n const onChange = (e: React.ChangeEvent) => {\n dispatch({\n type: 'chat',\n payload: e.target.value,\n });\n };\n\n return React.useMemo(\n () => {\n addLogHack('\u804a\u5929\u5ba4\u7ec4\u4ef6\u91cd\u65b0\u6e32\u67d3\ud83d\udc90');\n return (\n \n

\u804a\u5929\u5ba4

\n \u5f53\u524d\u6d88\u606f\u662f: {message}\n \n
\n )\n },\n [message],\n )\n}\n")),Object(c.b)("p",null,"\u6ce8\u610f\u8fd9\u79cd\u4f18\u5316\u4e0b\uff0c",Object(c.b)("inlineCode",{parentName:"p"},"Chat"),"\u7ec4\u4ef6\u8fd8\u4f1a\u91cd\u65b0\u8fd0\u884c\uff0c\u4f46\u662freturn\u7684jsx\u5728",Object(c.b)("inlineCode",{parentName:"p"},"message"),"\u4e0d\u53d1\u751f\u6539\u53d8\u7684\u60c5\u51b5\u4e0b\u4e0d\u4f1a\u6539\u53d8\uff0c\u6240\u4ee5\u4e5f\u4e0d\u4f1a\u6709\u8017\u8d39\u6027\u80fd\u7684reconciler\u6d41\u7a0b\u4e86\u3002"))}E&&E===Object(E)&&Object.isExtensible(E)&&Object.defineProperty(E,"__filemeta",{enumerable:!0,configurable:!0,value:{name:"MDXContent",filename:"example/Example.mdx"}}),E.isMDXComponent=!0},"./example/index.css":function(e,n,t){},"./example/store.ts":function(e,n,t){"use strict";var a=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/regenerator/index.js"),r=t.n(a),o=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/asyncToGenerator.js"),A=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/toConsumableArray.js"),c=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/defineProperty.js"),s=t("./node_modules/_@babel_runtime@7.7.1@@babel/runtime/helpers/esm/slicedToArray.js"),i=t("react"),u=t.n(i),l=t("./node_modules/_hoist-non-react-statics@3.3.0@hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js"),d=t.n(l);function g(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function m(e){for(var n=1;n=0&&o._disposeHandlers.splice(n,1)},check:w,apply:O,status:function(e){if(!e)return i;_.push(e)},addStatusHandler:function(e){_.push(e)},removeStatusHandler:function(e){var o=_.indexOf(e);o>=0&&_.splice(o,1)},data:a[e]};return t=void 0,o}var _=[],i="idle";function j(e){i=e;for(var o=0;o<_.length;o++)_[o].call(null,e)}var p,f,h,g=0,v=0,y={},b={},x={};function k(e){return+e+""===e?+e:e}function w(e){if("idle"!==i)throw new Error("check() is only allowed in idle status");return l=e,j("check"),(o=d,o=o||1e4,new Promise((function(e,n){if("undefined"===typeof XMLHttpRequest)return n(new Error("No browser support"));try{var m=new XMLHttpRequest,t=T.p+""+s+".hot-update.json";m.open("GET",t,!0),m.timeout=o,m.send(null)}catch(l){return n(l)}m.onreadystatechange=function(){if(4===m.readyState)if(0===m.status)n(new Error("Manifest request to "+t+" timed out."));else if(404===m.status)e();else if(200!==m.status&&304!==m.status)n(new Error("Manifest request to "+t+" failed."));else{try{var o=JSON.parse(m.responseText)}catch(l){return void n(l)}e(o)}}}))).then((function(e){if(!e)return j("idle"),null;b={},y={},x=e.c,h=e.h,j("prepare");var o=new Promise((function(e,o){p={resolve:e,reject:o}}));for(var n in f={},N)E(n);return"prepare"===i&&0===v&&0===g&&z(),o}));var o}function E(e){x[e]?(b[e]=!0,g++,function(e){var o=document.createElement("script");o.charset="utf-8",o.src=T.p+""+e+"."+s+".hot-update.js",o.crossOrigin="anonymous",document.head.appendChild(o)}(e)):y[e]=!0}function z(){j("ready");var e=p;if(p=null,e)if(l)Promise.resolve().then((function(){return O(l)})).then((function(o){e.resolve(o)}),(function(o){e.reject(o)}));else{var o=[];for(var n in f)Object.prototype.hasOwnProperty.call(f,n)&&o.push(k(n));e.resolve(o)}}function O(o){if("ready"!==i)throw new Error("apply() is only allowed in ready status");var n,m,t,l,d;function c(e){for(var o=[e],n={},m=o.map((function(e){return{chain:[e],id:e}}));m.length>0;){var t=m.pop(),s=t.id,d=t.chain;if((l=P[s])&&!l.hot._selfAccepted){if(l.hot._selfDeclined)return{type:"self-declined",chain:d,moduleId:s};if(l.hot._main)return{type:"unaccepted",chain:d,moduleId:s};for(var a=0;a ")),b.type){case"self-declined":o.onDeclined&&o.onDeclined(b),o.ignoreDeclined||(w=new Error("Aborted because of self decline: "+b.moduleId+O));break;case"declined":o.onDeclined&&o.onDeclined(b),o.ignoreDeclined||(w=new Error("Aborted because of declined dependency: "+b.moduleId+" in "+b.parentId+O));break;case"unaccepted":o.onUnaccepted&&o.onUnaccepted(b),o.ignoreUnaccepted||(w=new Error("Aborted because "+d+" is not accepted"+O));break;case"accepted":o.onAccepted&&o.onAccepted(b),E=!0;break;case"disposed":o.onDisposed&&o.onDisposed(b),z=!0;break;default:throw new Error("Unexception type "+b.type)}if(w)return j("abort"),Promise.reject(w);if(E)for(d in g[d]=f[d],u(p,b.outdatedModules),b.outdatedDependencies)Object.prototype.hasOwnProperty.call(b.outdatedDependencies,d)&&(_[d]||(_[d]=[]),u(_[d],b.outdatedDependencies[d]));z&&(u(p,[b.moduleId]),g[d]=v)}var A,D=[];for(m=0;m0;)if(d=H.pop(),l=P[d]){var q={},B=l.hot._disposeHandlers;for(t=0;t=0&&I.parents.splice(A,1))}}for(d in _)if(Object.prototype.hasOwnProperty.call(_,d)&&(l=P[d]))for(C=_[d],t=0;t=0&&l.children.splice(A,1);for(d in j("apply"),s=h,g)Object.prototype.hasOwnProperty.call(g,d)&&(e[d]=g[d]);var M=null;for(d in _)if(Object.prototype.hasOwnProperty.call(_,d)&&(l=P[d])){C=_[d];var L=[];for(m=0;m0&&void 0!==arguments[0]?arguments[0]:u;j(),s.a.render(t.a.createElement(e,null),f,p)}(u)},"./node_modules/_moment@2.24.0@moment/locale sync recursive ^\\.\\/.*$":function(e,o,n){var m={"./af":"./node_modules/_moment@2.24.0@moment/locale/af.js","./af.js":"./node_modules/_moment@2.24.0@moment/locale/af.js","./ar":"./node_modules/_moment@2.24.0@moment/locale/ar.js","./ar-dz":"./node_modules/_moment@2.24.0@moment/locale/ar-dz.js","./ar-dz.js":"./node_modules/_moment@2.24.0@moment/locale/ar-dz.js","./ar-kw":"./node_modules/_moment@2.24.0@moment/locale/ar-kw.js","./ar-kw.js":"./node_modules/_moment@2.24.0@moment/locale/ar-kw.js","./ar-ly":"./node_modules/_moment@2.24.0@moment/locale/ar-ly.js","./ar-ly.js":"./node_modules/_moment@2.24.0@moment/locale/ar-ly.js","./ar-ma":"./node_modules/_moment@2.24.0@moment/locale/ar-ma.js","./ar-ma.js":"./node_modules/_moment@2.24.0@moment/locale/ar-ma.js","./ar-sa":"./node_modules/_moment@2.24.0@moment/locale/ar-sa.js","./ar-sa.js":"./node_modules/_moment@2.24.0@moment/locale/ar-sa.js","./ar-tn":"./node_modules/_moment@2.24.0@moment/locale/ar-tn.js","./ar-tn.js":"./node_modules/_moment@2.24.0@moment/locale/ar-tn.js","./ar.js":"./node_modules/_moment@2.24.0@moment/locale/ar.js","./az":"./node_modules/_moment@2.24.0@moment/locale/az.js","./az.js":"./node_modules/_moment@2.24.0@moment/locale/az.js","./be":"./node_modules/_moment@2.24.0@moment/locale/be.js","./be.js":"./node_modules/_moment@2.24.0@moment/locale/be.js","./bg":"./node_modules/_moment@2.24.0@moment/locale/bg.js","./bg.js":"./node_modules/_moment@2.24.0@moment/locale/bg.js","./bm":"./node_modules/_moment@2.24.0@moment/locale/bm.js","./bm.js":"./node_modules/_moment@2.24.0@moment/locale/bm.js","./bn":"./node_modules/_moment@2.24.0@moment/locale/bn.js","./bn.js":"./node_modules/_moment@2.24.0@moment/locale/bn.js","./bo":"./node_modules/_moment@2.24.0@moment/locale/bo.js","./bo.js":"./node_modules/_moment@2.24.0@moment/locale/bo.js","./br":"./node_modules/_moment@2.24.0@moment/locale/br.js","./br.js":"./node_modules/_moment@2.24.0@moment/locale/br.js","./bs":"./node_modules/_moment@2.24.0@moment/locale/bs.js","./bs.js":"./node_modules/_moment@2.24.0@moment/locale/bs.js","./ca":"./node_modules/_moment@2.24.0@moment/locale/ca.js","./ca.js":"./node_modules/_moment@2.24.0@moment/locale/ca.js","./cs":"./node_modules/_moment@2.24.0@moment/locale/cs.js","./cs.js":"./node_modules/_moment@2.24.0@moment/locale/cs.js","./cv":"./node_modules/_moment@2.24.0@moment/locale/cv.js","./cv.js":"./node_modules/_moment@2.24.0@moment/locale/cv.js","./cy":"./node_modules/_moment@2.24.0@moment/locale/cy.js","./cy.js":"./node_modules/_moment@2.24.0@moment/locale/cy.js","./da":"./node_modules/_moment@2.24.0@moment/locale/da.js","./da.js":"./node_modules/_moment@2.24.0@moment/locale/da.js","./de":"./node_modules/_moment@2.24.0@moment/locale/de.js","./de-at":"./node_modules/_moment@2.24.0@moment/locale/de-at.js","./de-at.js":"./node_modules/_moment@2.24.0@moment/locale/de-at.js","./de-ch":"./node_modules/_moment@2.24.0@moment/locale/de-ch.js","./de-ch.js":"./node_modules/_moment@2.24.0@moment/locale/de-ch.js","./de.js":"./node_modules/_moment@2.24.0@moment/locale/de.js","./dv":"./node_modules/_moment@2.24.0@moment/locale/dv.js","./dv.js":"./node_modules/_moment@2.24.0@moment/locale/dv.js","./el":"./node_modules/_moment@2.24.0@moment/locale/el.js","./el.js":"./node_modules/_moment@2.24.0@moment/locale/el.js","./en-SG":"./node_modules/_moment@2.24.0@moment/locale/en-SG.js","./en-SG.js":"./node_modules/_moment@2.24.0@moment/locale/en-SG.js","./en-au":"./node_modules/_moment@2.24.0@moment/locale/en-au.js","./en-au.js":"./node_modules/_moment@2.24.0@moment/locale/en-au.js","./en-ca":"./node_modules/_moment@2.24.0@moment/locale/en-ca.js","./en-ca.js":"./node_modules/_moment@2.24.0@moment/locale/en-ca.js","./en-gb":"./node_modules/_moment@2.24.0@moment/locale/en-gb.js","./en-gb.js":"./node_modules/_moment@2.24.0@moment/locale/en-gb.js","./en-ie":"./node_modules/_moment@2.24.0@moment/locale/en-ie.js","./en-ie.js":"./node_modules/_moment@2.24.0@moment/locale/en-ie.js","./en-il":"./node_modules/_moment@2.24.0@moment/locale/en-il.js","./en-il.js":"./node_modules/_moment@2.24.0@moment/locale/en-il.js","./en-nz":"./node_modules/_moment@2.24.0@moment/locale/en-nz.js","./en-nz.js":"./node_modules/_moment@2.24.0@moment/locale/en-nz.js","./eo":"./node_modules/_moment@2.24.0@moment/locale/eo.js","./eo.js":"./node_modules/_moment@2.24.0@moment/locale/eo.js","./es":"./node_modules/_moment@2.24.0@moment/locale/es.js","./es-do":"./node_modules/_moment@2.24.0@moment/locale/es-do.js","./es-do.js":"./node_modules/_moment@2.24.0@moment/locale/es-do.js","./es-us":"./node_modules/_moment@2.24.0@moment/locale/es-us.js","./es-us.js":"./node_modules/_moment@2.24.0@moment/locale/es-us.js","./es.js":"./node_modules/_moment@2.24.0@moment/locale/es.js","./et":"./node_modules/_moment@2.24.0@moment/locale/et.js","./et.js":"./node_modules/_moment@2.24.0@moment/locale/et.js","./eu":"./node_modules/_moment@2.24.0@moment/locale/eu.js","./eu.js":"./node_modules/_moment@2.24.0@moment/locale/eu.js","./fa":"./node_modules/_moment@2.24.0@moment/locale/fa.js","./fa.js":"./node_modules/_moment@2.24.0@moment/locale/fa.js","./fi":"./node_modules/_moment@2.24.0@moment/locale/fi.js","./fi.js":"./node_modules/_moment@2.24.0@moment/locale/fi.js","./fo":"./node_modules/_moment@2.24.0@moment/locale/fo.js","./fo.js":"./node_modules/_moment@2.24.0@moment/locale/fo.js","./fr":"./node_modules/_moment@2.24.0@moment/locale/fr.js","./fr-ca":"./node_modules/_moment@2.24.0@moment/locale/fr-ca.js","./fr-ca.js":"./node_modules/_moment@2.24.0@moment/locale/fr-ca.js","./fr-ch":"./node_modules/_moment@2.24.0@moment/locale/fr-ch.js","./fr-ch.js":"./node_modules/_moment@2.24.0@moment/locale/fr-ch.js","./fr.js":"./node_modules/_moment@2.24.0@moment/locale/fr.js","./fy":"./node_modules/_moment@2.24.0@moment/locale/fy.js","./fy.js":"./node_modules/_moment@2.24.0@moment/locale/fy.js","./ga":"./node_modules/_moment@2.24.0@moment/locale/ga.js","./ga.js":"./node_modules/_moment@2.24.0@moment/locale/ga.js","./gd":"./node_modules/_moment@2.24.0@moment/locale/gd.js","./gd.js":"./node_modules/_moment@2.24.0@moment/locale/gd.js","./gl":"./node_modules/_moment@2.24.0@moment/locale/gl.js","./gl.js":"./node_modules/_moment@2.24.0@moment/locale/gl.js","./gom-latn":"./node_modules/_moment@2.24.0@moment/locale/gom-latn.js","./gom-latn.js":"./node_modules/_moment@2.24.0@moment/locale/gom-latn.js","./gu":"./node_modules/_moment@2.24.0@moment/locale/gu.js","./gu.js":"./node_modules/_moment@2.24.0@moment/locale/gu.js","./he":"./node_modules/_moment@2.24.0@moment/locale/he.js","./he.js":"./node_modules/_moment@2.24.0@moment/locale/he.js","./hi":"./node_modules/_moment@2.24.0@moment/locale/hi.js","./hi.js":"./node_modules/_moment@2.24.0@moment/locale/hi.js","./hr":"./node_modules/_moment@2.24.0@moment/locale/hr.js","./hr.js":"./node_modules/_moment@2.24.0@moment/locale/hr.js","./hu":"./node_modules/_moment@2.24.0@moment/locale/hu.js","./hu.js":"./node_modules/_moment@2.24.0@moment/locale/hu.js","./hy-am":"./node_modules/_moment@2.24.0@moment/locale/hy-am.js","./hy-am.js":"./node_modules/_moment@2.24.0@moment/locale/hy-am.js","./id":"./node_modules/_moment@2.24.0@moment/locale/id.js","./id.js":"./node_modules/_moment@2.24.0@moment/locale/id.js","./is":"./node_modules/_moment@2.24.0@moment/locale/is.js","./is.js":"./node_modules/_moment@2.24.0@moment/locale/is.js","./it":"./node_modules/_moment@2.24.0@moment/locale/it.js","./it-ch":"./node_modules/_moment@2.24.0@moment/locale/it-ch.js","./it-ch.js":"./node_modules/_moment@2.24.0@moment/locale/it-ch.js","./it.js":"./node_modules/_moment@2.24.0@moment/locale/it.js","./ja":"./node_modules/_moment@2.24.0@moment/locale/ja.js","./ja.js":"./node_modules/_moment@2.24.0@moment/locale/ja.js","./jv":"./node_modules/_moment@2.24.0@moment/locale/jv.js","./jv.js":"./node_modules/_moment@2.24.0@moment/locale/jv.js","./ka":"./node_modules/_moment@2.24.0@moment/locale/ka.js","./ka.js":"./node_modules/_moment@2.24.0@moment/locale/ka.js","./kk":"./node_modules/_moment@2.24.0@moment/locale/kk.js","./kk.js":"./node_modules/_moment@2.24.0@moment/locale/kk.js","./km":"./node_modules/_moment@2.24.0@moment/locale/km.js","./km.js":"./node_modules/_moment@2.24.0@moment/locale/km.js","./kn":"./node_modules/_moment@2.24.0@moment/locale/kn.js","./kn.js":"./node_modules/_moment@2.24.0@moment/locale/kn.js","./ko":"./node_modules/_moment@2.24.0@moment/locale/ko.js","./ko.js":"./node_modules/_moment@2.24.0@moment/locale/ko.js","./ku":"./node_modules/_moment@2.24.0@moment/locale/ku.js","./ku.js":"./node_modules/_moment@2.24.0@moment/locale/ku.js","./ky":"./node_modules/_moment@2.24.0@moment/locale/ky.js","./ky.js":"./node_modules/_moment@2.24.0@moment/locale/ky.js","./lb":"./node_modules/_moment@2.24.0@moment/locale/lb.js","./lb.js":"./node_modules/_moment@2.24.0@moment/locale/lb.js","./lo":"./node_modules/_moment@2.24.0@moment/locale/lo.js","./lo.js":"./node_modules/_moment@2.24.0@moment/locale/lo.js","./lt":"./node_modules/_moment@2.24.0@moment/locale/lt.js","./lt.js":"./node_modules/_moment@2.24.0@moment/locale/lt.js","./lv":"./node_modules/_moment@2.24.0@moment/locale/lv.js","./lv.js":"./node_modules/_moment@2.24.0@moment/locale/lv.js","./me":"./node_modules/_moment@2.24.0@moment/locale/me.js","./me.js":"./node_modules/_moment@2.24.0@moment/locale/me.js","./mi":"./node_modules/_moment@2.24.0@moment/locale/mi.js","./mi.js":"./node_modules/_moment@2.24.0@moment/locale/mi.js","./mk":"./node_modules/_moment@2.24.0@moment/locale/mk.js","./mk.js":"./node_modules/_moment@2.24.0@moment/locale/mk.js","./ml":"./node_modules/_moment@2.24.0@moment/locale/ml.js","./ml.js":"./node_modules/_moment@2.24.0@moment/locale/ml.js","./mn":"./node_modules/_moment@2.24.0@moment/locale/mn.js","./mn.js":"./node_modules/_moment@2.24.0@moment/locale/mn.js","./mr":"./node_modules/_moment@2.24.0@moment/locale/mr.js","./mr.js":"./node_modules/_moment@2.24.0@moment/locale/mr.js","./ms":"./node_modules/_moment@2.24.0@moment/locale/ms.js","./ms-my":"./node_modules/_moment@2.24.0@moment/locale/ms-my.js","./ms-my.js":"./node_modules/_moment@2.24.0@moment/locale/ms-my.js","./ms.js":"./node_modules/_moment@2.24.0@moment/locale/ms.js","./mt":"./node_modules/_moment@2.24.0@moment/locale/mt.js","./mt.js":"./node_modules/_moment@2.24.0@moment/locale/mt.js","./my":"./node_modules/_moment@2.24.0@moment/locale/my.js","./my.js":"./node_modules/_moment@2.24.0@moment/locale/my.js","./nb":"./node_modules/_moment@2.24.0@moment/locale/nb.js","./nb.js":"./node_modules/_moment@2.24.0@moment/locale/nb.js","./ne":"./node_modules/_moment@2.24.0@moment/locale/ne.js","./ne.js":"./node_modules/_moment@2.24.0@moment/locale/ne.js","./nl":"./node_modules/_moment@2.24.0@moment/locale/nl.js","./nl-be":"./node_modules/_moment@2.24.0@moment/locale/nl-be.js","./nl-be.js":"./node_modules/_moment@2.24.0@moment/locale/nl-be.js","./nl.js":"./node_modules/_moment@2.24.0@moment/locale/nl.js","./nn":"./node_modules/_moment@2.24.0@moment/locale/nn.js","./nn.js":"./node_modules/_moment@2.24.0@moment/locale/nn.js","./pa-in":"./node_modules/_moment@2.24.0@moment/locale/pa-in.js","./pa-in.js":"./node_modules/_moment@2.24.0@moment/locale/pa-in.js","./pl":"./node_modules/_moment@2.24.0@moment/locale/pl.js","./pl.js":"./node_modules/_moment@2.24.0@moment/locale/pl.js","./pt":"./node_modules/_moment@2.24.0@moment/locale/pt.js","./pt-br":"./node_modules/_moment@2.24.0@moment/locale/pt-br.js","./pt-br.js":"./node_modules/_moment@2.24.0@moment/locale/pt-br.js","./pt.js":"./node_modules/_moment@2.24.0@moment/locale/pt.js","./ro":"./node_modules/_moment@2.24.0@moment/locale/ro.js","./ro.js":"./node_modules/_moment@2.24.0@moment/locale/ro.js","./ru":"./node_modules/_moment@2.24.0@moment/locale/ru.js","./ru.js":"./node_modules/_moment@2.24.0@moment/locale/ru.js","./sd":"./node_modules/_moment@2.24.0@moment/locale/sd.js","./sd.js":"./node_modules/_moment@2.24.0@moment/locale/sd.js","./se":"./node_modules/_moment@2.24.0@moment/locale/se.js","./se.js":"./node_modules/_moment@2.24.0@moment/locale/se.js","./si":"./node_modules/_moment@2.24.0@moment/locale/si.js","./si.js":"./node_modules/_moment@2.24.0@moment/locale/si.js","./sk":"./node_modules/_moment@2.24.0@moment/locale/sk.js","./sk.js":"./node_modules/_moment@2.24.0@moment/locale/sk.js","./sl":"./node_modules/_moment@2.24.0@moment/locale/sl.js","./sl.js":"./node_modules/_moment@2.24.0@moment/locale/sl.js","./sq":"./node_modules/_moment@2.24.0@moment/locale/sq.js","./sq.js":"./node_modules/_moment@2.24.0@moment/locale/sq.js","./sr":"./node_modules/_moment@2.24.0@moment/locale/sr.js","./sr-cyrl":"./node_modules/_moment@2.24.0@moment/locale/sr-cyrl.js","./sr-cyrl.js":"./node_modules/_moment@2.24.0@moment/locale/sr-cyrl.js","./sr.js":"./node_modules/_moment@2.24.0@moment/locale/sr.js","./ss":"./node_modules/_moment@2.24.0@moment/locale/ss.js","./ss.js":"./node_modules/_moment@2.24.0@moment/locale/ss.js","./sv":"./node_modules/_moment@2.24.0@moment/locale/sv.js","./sv.js":"./node_modules/_moment@2.24.0@moment/locale/sv.js","./sw":"./node_modules/_moment@2.24.0@moment/locale/sw.js","./sw.js":"./node_modules/_moment@2.24.0@moment/locale/sw.js","./ta":"./node_modules/_moment@2.24.0@moment/locale/ta.js","./ta.js":"./node_modules/_moment@2.24.0@moment/locale/ta.js","./te":"./node_modules/_moment@2.24.0@moment/locale/te.js","./te.js":"./node_modules/_moment@2.24.0@moment/locale/te.js","./tet":"./node_modules/_moment@2.24.0@moment/locale/tet.js","./tet.js":"./node_modules/_moment@2.24.0@moment/locale/tet.js","./tg":"./node_modules/_moment@2.24.0@moment/locale/tg.js","./tg.js":"./node_modules/_moment@2.24.0@moment/locale/tg.js","./th":"./node_modules/_moment@2.24.0@moment/locale/th.js","./th.js":"./node_modules/_moment@2.24.0@moment/locale/th.js","./tl-ph":"./node_modules/_moment@2.24.0@moment/locale/tl-ph.js","./tl-ph.js":"./node_modules/_moment@2.24.0@moment/locale/tl-ph.js","./tlh":"./node_modules/_moment@2.24.0@moment/locale/tlh.js","./tlh.js":"./node_modules/_moment@2.24.0@moment/locale/tlh.js","./tr":"./node_modules/_moment@2.24.0@moment/locale/tr.js","./tr.js":"./node_modules/_moment@2.24.0@moment/locale/tr.js","./tzl":"./node_modules/_moment@2.24.0@moment/locale/tzl.js","./tzl.js":"./node_modules/_moment@2.24.0@moment/locale/tzl.js","./tzm":"./node_modules/_moment@2.24.0@moment/locale/tzm.js","./tzm-latn":"./node_modules/_moment@2.24.0@moment/locale/tzm-latn.js","./tzm-latn.js":"./node_modules/_moment@2.24.0@moment/locale/tzm-latn.js","./tzm.js":"./node_modules/_moment@2.24.0@moment/locale/tzm.js","./ug-cn":"./node_modules/_moment@2.24.0@moment/locale/ug-cn.js","./ug-cn.js":"./node_modules/_moment@2.24.0@moment/locale/ug-cn.js","./uk":"./node_modules/_moment@2.24.0@moment/locale/uk.js","./uk.js":"./node_modules/_moment@2.24.0@moment/locale/uk.js","./ur":"./node_modules/_moment@2.24.0@moment/locale/ur.js","./ur.js":"./node_modules/_moment@2.24.0@moment/locale/ur.js","./uz":"./node_modules/_moment@2.24.0@moment/locale/uz.js","./uz-latn":"./node_modules/_moment@2.24.0@moment/locale/uz-latn.js","./uz-latn.js":"./node_modules/_moment@2.24.0@moment/locale/uz-latn.js","./uz.js":"./node_modules/_moment@2.24.0@moment/locale/uz.js","./vi":"./node_modules/_moment@2.24.0@moment/locale/vi.js","./vi.js":"./node_modules/_moment@2.24.0@moment/locale/vi.js","./x-pseudo":"./node_modules/_moment@2.24.0@moment/locale/x-pseudo.js","./x-pseudo.js":"./node_modules/_moment@2.24.0@moment/locale/x-pseudo.js","./yo":"./node_modules/_moment@2.24.0@moment/locale/yo.js","./yo.js":"./node_modules/_moment@2.24.0@moment/locale/yo.js","./zh-cn":"./node_modules/_moment@2.24.0@moment/locale/zh-cn.js","./zh-cn.js":"./node_modules/_moment@2.24.0@moment/locale/zh-cn.js","./zh-hk":"./node_modules/_moment@2.24.0@moment/locale/zh-hk.js","./zh-hk.js":"./node_modules/_moment@2.24.0@moment/locale/zh-hk.js","./zh-tw":"./node_modules/_moment@2.24.0@moment/locale/zh-tw.js","./zh-tw.js":"./node_modules/_moment@2.24.0@moment/locale/zh-tw.js"};function t(e){var o=l(e);return n(o)}function l(e){if(!n.o(m,e)){var o=new Error("Cannot find module '"+e+"'");throw o.code="MODULE_NOT_FOUND",o}return m[e]}t.keys=function(){return Object.keys(m)},t.resolve=l,e.exports=t,t.id="./node_modules/_moment@2.24.0@moment/locale sync recursive ^\\.\\/.*$"},0:function(e,o,n){e.exports=n("./.docz/app/index.jsx")},react:function(e,o){e.exports=window.React},"react-dom":function(e,o){e.exports=window.ReactDOM}}); --------------------------------------------------------------------------------