导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1.dva介绍
    • 1.1 前置知识
  • 2. 初始化项目
  • 3. 基本计数器
    • 3.1 src\index.js
    • 3.2 src\dva\index.js
  • 4. 支持effects
    • 4.1 src\index.js
    • 4.2 src\dva\index.js
  • 5.支持路由
    • 5.1 src\index.js
    • 5.2 src\dva\index.js
    • 5.3 src\dva\router.js
  • 6.支持跳转
    • 6.1 src\index.js
    • 6.2 src\dva\index.js
    • 6.3 src\dva\router.js
  • 7.支持自定义effects
    • 7.1 src\index.js
    • 7.2 src\dva\index.js
  • 8.支持钩子
    • 8.1 src\index.js
    • 8.2 src\dva\index.js
  • 8.版本
    • 8.1 src\index.js
    • 8.2 dva\index.js
    • 8.3 router.js
    • 8.4 createReducers.js
    • 8.5 createRootSaga.js
  • 8.参考

1.dva介绍 #

dva

1.1 前置知识 #

  • react
  • react-router-dom
  • redux
  • react-redux
  • connected-react-router
  • history
  • dva

2. 初始化项目 #

create-react-app zhufeng-dva-source
cd  zhufeng-dva-source
cnpm install dva redux react-redux react-router-dom connected-react-router history
npm start

3. 基本计数器 #

3.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from 'dva';
const app = dva();
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state){
            return {number:state.number+1};
        }
    }
});
const Counter = connect(
    state=>state.counter
)(props=>(
    <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:"counter/add"})}>+</button>
    </div>
));
app.router(()=><Counter/>);
app.start('#root');

3.2 src\dva\index.js #

src\dva\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore,combineReducers} from 'redux';
import {connect,Provider} from 'react-redux';
export {connect};
export default function(){
    const app = {
        _models:[],
        model,
        _router:null,
        router,
        start
    }
    function model(model){
        app._models.push(model);
    }
    function router(routerConfig){
        app._router = routerConfig;
    }
    function start(root){
        let reducers = {};
        for(let i=0;i<app._models.length;i++){
            let model = app._models[i];
            model.reducers = prefixNamespace(model.reducers,model.namespace);
            reducers[model.namespace] = function(state = model.state, action) {
                let actionType = action.type;
                let reducer = model.reducers[actionType];
                if (reducer) {
                    return reducer(state, action);
                }
                return state;
            };
        }
        let rootReducer = combineReducers(reducers);
        let store = createStore(rootReducer);
        let App = app._router;
        ReactDOM.render(<Provider store={store}><App/></Provider>,document.querySelector(root));
    }
    return app;
}
//https://github.com/dvajs/dva/blob/master/packages/dva-core/src/prefixNamespace.js
function prefixNamespace(reducers,namespace){
    return Object.keys(reducers).reduce((memo, key) => {
        const newKey = `${namespace}${NAMESPACE_SEPARATOR}${key}`;
        memo[newKey] = reducers[key];
        return memo;
    }, {});
}

4. 支持effects #

4.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from 'dva';
const app = dva();
+ function delay(ms) {
+    return new Promise((resolve,reject) => {
+        setTimeout(function () {
+            resolve();
+        },ms);
+    });
+ }
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state){
            return {number:state.number+1};
        }
    },
+    effects:{
+        *asyncAdd(action,{call,put}){
+          yield call(delay,1000);
+          yield put({type:'counter/add'});
+        }
+    }
});
const Counter = connect(
    state=>state.counter
)(props=>(
    <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:"counter/add"})}>+</button>
+        <button onClick={()=>props.dispatch({type:"counter/asyncAdd"})}>异步+</button>
    </div>
));
app.router(()=><Counter/>);
app.start('#root');

4.2 src\dva\index.js #

src\dva\index.js

import React from 'react';
import ReactDOM from 'react-dom';
+import {createStore,combineReducers,applyMiddleware} from 'redux';
import {connect,Provider} from 'react-redux';
+import createSagaMiddleware from 'redux-saga';
+import * as sagaEffects from 'redux-saga/effects';
export {connect};
export default function(){
    const app = {
        _models:[],
        model,
        _router:null,
        router,
        start
    }
    function model(model){
        app._models.push(model);
    }
    function router(routerConfig){
        app._router = routerConfig;
    }
    function start(root){
        let reducers = {};
        for(let i=0;i<app._models.length;i++){
            let model = app._models[i];
            reducers[model.namespace] = function(state=model.state,action){
                 let actionType = action.type;
                 let values = actionType.split('/');
                 if(values[0] === model.namespace){
                    let reducer = model.reducers[values[1]];
                    if(reducer){
                        return reducer(state,action);
                    }
                 }
                 return state;
            }
        }
        let rootReducer = combineReducers(reducers);
+        let sagaMiddleware = createSagaMiddleware();
+        function* rootSaga(){
+            let {takeEvery} =  sagaEffects;
+            for(const model of app._models){
+                for(const key in model.effects){
+                    yield takeEvery(model.namespace+'/'+key,function*(action){
+                        yield model.effects[key](action,sagaEffects);
+                    });
+                }
+            }
+        }
+        let store = createStore(rootReducer,applyMiddleware(sagaMiddleware));
+        sagaMiddleware.run(rootSaga);
        let App = app._router;
        ReactDOM.render(<Provider store={store}><App/></Provider>,document.querySelector(root));
    }
    return app;
}

5.支持路由 #

5.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from 'dva';
+import {Router,Route} from 'dva/router';
const app = dva();
function delay(ms) {
    return new Promise((resolve,reject) => {
        setTimeout(function () {
            resolve();
        },ms);
    });
}
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state){
            return {number:state.number+1};
        }
    },
    effects:{
        *asyncAdd(action,{call,put}){
          yield call(delay,1000);
          yield put({type:'counter/add'});
        }
    }
});
const Counter = connect(
    state=>state.counter
)(props=>(
    <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:"counter/add"})}>+</button>
        <button onClick={()=>props.dispatch({type:"counter/asyncAdd"})}>异步+</button>
    </div>
));
//index.js:1375 Warning: Please use `require("history").createHashHistory` instead of `require("history/createHashHistory")`. Support for the latter will be removed in the next major release.
//index.js:1375 Warning: [sagaEffects.put] counter/add should not be prefixed with namespace counter
+const Home = ()=><div>Home</div>
+app.router(({history})=>(
+    <Router history={history}>
+        <>
+            <Route path="/" exact={true} component={Home}/>
+            <Route path="/counter" component={Counter}/>
+        </>
+    </Router>
+));
app.start('#root');

5.2 src\dva\index.js #

src\dva\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import {connect,Provider} from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import * as sagaEffects from 'redux-saga/effects';
+import {createHashHistory} from 'history';
export {connect};
export default function(){
    const app = {
        _models:[],
        model,
        _router:null,
        router,
        start
    }
    function model(model){
        app._models.push(model);
    }
    function router(routerConfig){
        app._router = routerConfig;
    }
    function start(root){
        let reducers = {};
        for(let i=0;i<app._models.length;i++){
            let model = app._models[i];
            reducers[model.namespace] = function(state=model.state,action){
                 let actionType = action.type;
                 let values = actionType.split('/');
                 if(values[0] === model.namespace){
                    let reducer = model.reducers[values[1]];
                    if(reducer){
                        return reducer(state,action);
                    }
                 }
                 return state;
            }
        }
        let rootReducer = combineReducers(reducers);
        let sagaMiddleware = createSagaMiddleware();
        function* rootSaga(){
            let {takeEvery} =  sagaEffects;
            for(const model of app._models){
                for(const key in model.effects){
                    yield takeEvery(model.namespace+'/'+key,function*(action){
                        yield model.effects[key](action,sagaEffects);
                    });
                }
            }
        }
        let store = createStore(rootReducer,applyMiddleware(sagaMiddleware));
        sagaMiddleware.run(rootSaga);
+        let history = createHashHistory();
+        let App = app._router({history});
+        ReactDOM.render(<Provider store={store}>{App}</Provider>,document.querySelector(root));
    }
    return app;
}

5.3 src\dva\router.js #

src\dva\router.js

export * from 'react-router-dom';

6.支持跳转 #

6.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from './dva';
+ import {Router,Route,routerRedux} from './dva/router';
const app = dva();
function delay(ms) {
    return new Promise((resolve,reject) => {
        setTimeout(function () {
            resolve();
        },ms);
    });
}
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state){
            return {number:state.number+1};
        }
    },
    effects:{
        *asyncAdd(action,{call,put}){
          yield call(delay,1000);
          yield put({type:'counter/add'});
        },
+       *goto({to}, { put }) {
+         yield put(routerRedux.push(to));
+       }
    }
});
const Counter = connect(
    state=>state.counter
)(props=>(
    <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:"counter/add"})}>+</button>
        <button onClick={()=>props.dispatch({type:"counter/asyncAdd"})}>异步+</button>
+       <button onClick={()=>props.dispatch({type:"counter/goto",to:'/'})}>跳转到/</button>
    </div>
));
//index.js:1375 Warning: Please use `require("history").createHashHistory` instead of `require("history/createHashHistory")`. Support for the latter will be removed in the next major release.
//index.js:1375 Warning: [sagaEffects.put] counter/add should not be prefixed with namespace counter
const Home = ()=><div>Home</div>
app.router(({history})=>(
        <>
            <Route path="/" exact={true} component={Home}/>
            <Route path="/counter" component={Counter}/>
        </>
));
app.start('#root');

6.2 src\dva\index.js #

src\dva\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import {connect,Provider} from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import * as sagaEffects from 'redux-saga/effects';
import {createHashHistory} from 'history';
+import {
+  routerMiddleware,
+  connectRouter,
+  ConnectedRouter
+} from "connected-react-router";
+const NAMESPACE_SEPARATOR = "/";
export {connect};
export default function(){
    const app = {
        _models:[],
        model,
        _router:null,
        router,
        start
    }
    function model(model){
        app._models.push(model);
    }
    function router(routerConfig){
        app._router = routerConfig;
    }
    function start(root){
+        let history = createHashHistory();
+        const reducers = {
+            router: connectRouter(history)
+        };
        for(let i=0;i<app._models.length;i++){
            let model = app._models[i];
            reducers[model.namespace] = function(state=model.state,action){
                 let actionType = action.type;
                 let [namespace,type] = actionType.split(NAMESPACE_SEPARATOR);
                 if(!type){
                   type = namespace;
                   namespace = model.namespace;
                 }
                 if(namespace === model.namespace){
                    let reducer = model.reducers[type];
                    if(reducer){
                        return reducer(state,action);
                    }
                 }
                 return state;
            }
        }
        let rootReducer = combineReducers(reducers);
        let sagaMiddleware = createSagaMiddleware();
        function* rootSaga(){
            let {takeEvery} =  sagaEffects;
            for(const model of app._models){
                for(const key in model.effects){
+                    yield takeEvery(model.namespace+NAMESPACE_SEPARATOR+key,function*(action){
+                        yield model.effects[key](action,sagaEffects);
+                    });
                }
            }
        }
+        let store = createStore(rootReducer,applyMiddleware(routerMiddleware(history),sagaMiddleware));
        sagaMiddleware.run(rootSaga);
+        let App = app._router({history,app});
+        ReactDOM.render(
+        <Provider store={store}>
+            <ConnectedRouter history={history}>
+              {App}
+            </ConnectedRouter >
+        </Provider>,document.querySelector(root));
    }
    return app;
}

6.3 src\dva\router.js #

src\dva\router.js

+module.exports = require('react-router-dom');
+module.exports.routerRedux = require('connected-react-router');

7.支持自定义effects #

7.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from 'dva';
import {Router,Route,routerRedux} from 'dva/router';
const app = dva();
function delay(ms) {
    return new Promise((resolve,reject) => {
        setTimeout(function () {
            resolve();
        },ms);
    });
}
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state,){
            return {number:state.number+1};
        },
        increment(state,{payload}){
           return {number:state.number+payload||1};
        }
    },
    effects:{
       *asyncAdd(action,{call,put}){
          yield call(delay,1000);
          yield put({type:'counter/add'});
       },
       *goto({to}, { put }) {
         yield put(routerRedux.push(to));
       },
+       addWatchers:[
+           function*({take,put,call}){
+               for(let i=0;i<3;i++){
+                   const {payload} = yield take('counter/addWatcher');
+                   yield call(delay,1000);
+                   yield put({type:'counter/increment',payload});
+               }
+               alert('不能再加了');
+           },
+           {type:'watcher'}
+       ]
    }
});
const Counter = connect(
    state=>state.counter
)(props=>(
    <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:"counter/add"})}>+</button>
        <button onClick={()=>props.dispatch({type:"counter/asyncAdd"})}>异步+</button>
+        <button onClick={()=>props.dispatch({type:"counter/addWatcher",payload:2})}>自定义异步+2</button>
        <button onClick={()=>props.dispatch({type:"counter/goto",to:'/'})}>跳转到/</button>
    </div>
));
const Home = ()=><div>Home</div>
app.router(({history})=>(
    <Router history={history}>
        <>
            <Route path="/" exact={true} component={Home}/>
            <Route path="/counter" component={Counter}/>
        </>
    </Router>
));
app.start('#root');

7.2 src\dva\index.js #

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import {connect,Provider} from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import * as sagaEffects from 'redux-saga/effects';
import {createHashHistory} from 'history';
import {
  routerMiddleware,
  connectRouter,
  ConnectedRouter
} from "connected-react-router";
export {connect};
export default function(){
    const app = {
        _models:[],
        model,
        _router:null,
        router,
        start
    }
    function model(model){
        app._models.push(model);
    }
    function router(routerConfig){
        app._router = routerConfig;
    }
    function start(root){
        let history = createHashHistory();
        const reducers = {
            router: connectRouter(history)
        };
        for(let i=0;i<app._models.length;i++){
            let model = app._models[i];
            reducers[model.namespace] = function(state=model.state,action){
                 let actionType = action.type;
                 let values = actionType.split('/');
                 if(values[0] === model.namespace){
                    let reducer = model.reducers[values[1]];
                    if(reducer){
                        return reducer(state,action);
                    }
                 }
                 return state;
            }
        }
        let rootReducer = combineReducers(reducers);
         let sagaMiddleware = createSagaMiddleware();
        function* rootSaga(){
            let {takeEvery} =  sagaEffects;
            for(const model of app._models){
+                for(const key in model.effects){
+                    let effect = model.effects[key];
+                    if(Array.isArray(effect) && effect[1].type == 'watcher'){
+                       yield sagaEffects.fork(effect[0],sagaEffects);
+                    }else{
+                      yield takeEvery(model.namespace+'/'+key,function*(action){
+                        yield model.effects[key](action,sagaEffects);
+                      });
+                    }
+                }
+            }
        }
        let store = createStore(rootReducer,applyMiddleware(routerMiddleware(history),sagaMiddleware));
        sagaMiddleware.run(rootSaga);
        let App = app._router({history,app});
        ReactDOM.render(
        <Provider store={store}>
            <ConnectedRouter history={history}>
              {App}
            </ConnectedRouter >
        </Provider>,document.querySelector(root));
    }
    return app;
}

8.支持钩子 #

8.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from 'dva';
import {Router,Route,routerRedux} from 'dva/router';
+import { message } from 'antd';
+import logger from 'redux-logger';
+import {createBrowserHistory} from 'history';
+import 'antd/dist/antd.css'
+const SHOW = 'SHOW';
+const HIDE = 'HIDE';
+const initialState = {
+    global: false,
+    models: {},
+    effects: {}
+};
+const app = dva({
+    history:createBrowserHistory(),//自定义history
+    initialState:{counter:{number:5}},//自定义初始状态
+    //effect 执行错误或 subscription 通过 done 主动抛错时触发,可用于管理全局出错状态
+    onError:((err, dispatch) => {message.error(err.message)}),
+    //在 action 被 dispatch 时触发,用于注册 redux 中间件。支持函数或函数数组格式
+    onAction:logger,
+    //state 改变时触发,可用于同步 state 到 localStorage,服务器端等
+    onStateChange:(state)=>{console.log(state)},
+    //封装 reducer 执行。比如借助 redux-undo 实现 redo/undo
+    onReducer:reducer=>(state,action)=>{//增加额外的reducer
+      localStorage.setItem('action',JSON.stringify(action));
+      return reducer(state,action);
+    },
+    //封装 effect 执行。比如 dva-loading 基于此实现了自动处理 loading 状态。
+    onEffect:(effect, { put }, model, actionType)=>{
+      const { namespace } = model;
+      return function*(...args) {
+        yield put({ type: SHOW, payload: { namespace, actionType } });
+        yield effect(...args);
+        yield put({ type: HIDE, payload: { namespace, actionType } });
+      };
+    },
+    //指定额外的 reducer,比如 redux-form 需要指定额外的 form reducer:
+    extraReducers:{
+      loading(state = initialState, { type, payload }) {
+        const { namespace, actionType } = payload || {};
+        switch(type){
+          case SHOW:
+            return {global:true,models: { ...state.models, [namespace]: true }, effects: { ...state.effects, [actionType]: true },};
+          case HIDE:
+            const effects = { ...state.effects, [actionType]: false };
+            const models = { ...state.models, [namespace]: false };
+            const global = Object.keys(models).some(namespace => {
+                return models[namespace];
+            });
+            return {global,models,effects};
+          default:
+            return state;  
+        }
+      }
+    },
+    //指定额外的 StoreEnhancer ,比如结合 redux-persist 的使用:
+    //指定额外的 StoreEnhancer
+    //它的参数是创建store的函数(store creator),返回值是一个可以创建功能更加强大的store的函数(enhanced store creator)
+    extraEnhancers:(createStore)=>{
+       return (...args)=>{
+           let store = createStore(...args);
+           console.log('返回更强大的store')
+           return {...store,more(){console.log('更强大的强能')}};
+       }
+    }
+});
function delay(ms) {
    return new Promise((resolve,reject) => {
        setTimeout(function () {
            resolve();
        },ms);
    });
}
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state,){
            return {number:state.number+1};
        },
        increment(state,{payload}){
           return {number:state.number+payload||1};
        }
    },
    effects:{
       *asyncAdd(action,{call,put}){
          yield call(delay,1000);
          yield put({type:'counter/add'});
       },
       *goto({to}, { put }) {
         yield put(routerRedux.push(to));
       },
       addWatchers:[
           function*({take,put,call}){
               for(let i=0;i<3;i++){
                   const {payload} = yield take('counter/addWatcher');
                   yield call(delay,1000);
                   yield put({type:'counter/increment',payload});
               }
               alert('不能再加了');
           },
           {type:'watcher'}
       ]
    }
});
const Counter = connect(
    state=>state.counter
)(props=>(
    <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:"counter/add"})}>+</button>
        <button onClick={()=>props.dispatch({type:"counter/asyncAdd"})}>异步+</button>
        <button onClick={()=>props.dispatch({type:"counter/addWatcher",payload:2})}>自定义异步+2</button>
        <button onClick={()=>props.dispatch({type:"counter/goto",to:'/'})}>跳转到/</button>
    </div>
));
const Home = ()=><div>Home</div>
app.router(({history})=>(
    <Router history={history}>
        <>
            <Route path="/" exact={true} component={Home}/>
            <Route path="/counter" component={Counter}/>
        </>
    </Router>
));
app.start('#root');

8.2 src\dva\index.js #

src\dva\index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { connect, Provider } from "react-redux";
import createSagaMiddleware from "redux-saga";
import * as sagaEffects from "redux-saga/effects";
import { createHashHistory } from "history";
import {
  routerMiddleware,
  connectRouter,
  ConnectedRouter
} from "connected-react-router";
const NAMESPACE_SEPARATOR = "/";
export { connect };
export default function(options) {
  const app = {
    _models: [],
    model,
    _router: null,
    router,
    start
  };
  function model(model) {
    app._models.push(model);
  }
  function router(routerConfig) {
    app._router = routerConfig;
  }
+  let history = options.history||createHashHistory();
  function start(root) {
-  let history = createHashHistory();
+    let reducers = {
+      router: connectRouter(history)
+    };
+    if(options.extraReducers){
+        reducers = {...reducers,...options.extraReducers};
+    }
    for (let i = 0; i < app._models.length; i++) {
      let model = app._models[i];
      reducers[model.namespace] = function(state = model.state, action) {
        let actionType = action.type;
        let values = actionType.split(NAMESPACE_SEPARATOR);
        if (values[0] === model.namespace) {
          let reducer = model.reducers[values[1]];
          if (reducer) {
            return reducer(state, action);
          }
        }
        return state;
      };
    }
+    let combinedReducer  = combineReducers(reducers);
+    let rootReducer = function(state,action){
+        let newState =  combinedReducer(state,action);
+        options.onStateChange&&options.onStateChange(newState);
+        return newState;
+    }
+    if(options.onReducer){
+        rootReducer = options.onReducer(rootReducer);
+    }
    let sagaMiddleware = createSagaMiddleware();
    function* rootSaga() {
      let { takeEvery } = sagaEffects;
      for (const model of app._models) {
        for (const key in model.effects) {
          let effect = model.effects[key];
          if (Array.isArray(effect) && effect[1].type == "watcher") {
            yield sagaEffects.fork(effect[0], sagaEffects);
          } else {
            yield takeEvery(
              model.namespace + NAMESPACE_SEPARATOR + key,
              function*(action) {
                yield model.effects[key](action, sagaEffects);
              }
            );
          }
        }
      }
    }
+    if(options.onAction){
+        if(typeof options.onAction == 'function'){
+            options.onAction = [options.onAction];
+        }
+    }else{
+        options.onAction=[];
+    }
+    let enhancedCreateStore;
+    if(options.extraEnhancers){
+        enhancedCreateStore = options.extraEnhancers(createStore);
+    }
+    let store = enhancedCreateStore(
+      rootReducer,
+      applyMiddleware(routerMiddleware(history), sagaMiddleware,...options.onAction)
+    );
    sagaMiddleware.run(rootSaga);
    let App = app._router({ history, app });
    ReactDOM.render(
      <Provider store={store}>
        <ConnectedRouter history={history}>{App}</ConnectedRouter>
      </Provider>,
      document.querySelector(root)
    );
  }
  return app;
}

8.版本 #

8.1 src\index.js #

src\index.js

import React from 'react';
import dva,{connect} from './dva';
import {Router,Route,routerRedux} from './dva/router';
import {createBrowserHistory} from 'history';
import {message} from 'antd';
import 'antd/dist/antd.css';
import logger from 'redux-logger';
const SHOW = 'SHOW';
const HIDE = 'HIDE';
const initialLoadingState = {
    global:false,//只有有任何一个saga执行的它就是为true,只有所有的saga都不执行它才为false
    models:{},//里面放着哪个model有saga正在执行
    effects:{},//model中的哪个saga正在执行
}
//routerRedux= connected-react-redux导出对象
const app = dva({
    history:createBrowserHistory(),//指定用哪个历史对象
    initialState:{counter:{number:5}}, //初始状态
    //当effect执行错误会走到这里
    onError:(err,dispatch)=>{message.error(err.toString())},
    //值可能是一个中间件函数,也可能是一个中间件的数组
    onAction:logger,
    //当仓库中状态发生改变的时候,执行此函数
    onStateChange:state=>console.log('onStateChange',state),
    //onReducer 是用来增强reducer的
    onReducer:reducer=>(state,action)=>{
        let newState =  reducer(state,action);
        //newState.counter.number*=2;
        return newState;
    },
    //封闭effect的执行 dva-loading 老的saga effects model 动作类型
    onEffect:(effect,{put},model,actionType)=>{
        const {namespace} = model;
        return function*(...args){
            yield put({type:SHOW,payload:{namespace,actionType}});//namespace=counter actionType asyncAdd
            yield effect(...args);
            yield put({type:HIDE,payload:{namespace,actionType}});
        }
    },
    //额外的reducer对象 
    extraReducers:{
        //当向仓库派发动作的时候
        loading(state=initialLoadingState,{type,payload}){
            const {namespace,actionType} = payload||{};//namespace =counter actionType -counter/asyncAdd
            switch(type){
                case SHOW:
                  return {
                      global:true,//只要有一个effect执行了,它就为true
                      models:{...state.models,[namespace]:true},
                      effects:{...state.effects,[actionType]:true}
                  }
                case HIDE:
                  const effects =  {...state.effects,[actionType]:false};
                  const models = {...state.models,[namespace]:false};
                  const global = Object.keys(models).some(namespace=>models[namespace]);
                  return {effects,models,global};
                default:
                  return state;
            }

        }
    },
    //额外的增强器  用来增强createStore
    extraEnhancers:createStore=>{
        return (...args)=>{
            let store = createStore(...args);
            console.log('我正在创建仓库');
            return store;
        }
    }
});
const delay = ms=>new Promise(function(resolve){
    setTimeout(() => {resolve();}, ms);
});
app.model({
    namespace:'counter',
    state:{number:0},
    reducers:{
        add(state,{payload}){
            return {number:state.number+(payload||1)};
        }
    },
    effects:{
        *asyncAdd(action,{put,call}){
            yield call(delay,1000);
            throw new Error('异步加1失败');
            yield put({type:'counter/add'});
        },
        *goto({payload},{put}){
            yield put(routerRedux.push(payload));//connected-react-router
        },
        addWatcher:[
            function*({take,put,call}){
                for(let i=0;i<3;i++){
                    const {payload} = yield take('counter/addWatcher');
                    yield call(delay,1000);
                    yield put({type:'counter/add',payload});
                }
                console.log('已经到加最大值了,不能再加了')
            },
            {type:'watcher'}//监听器
        ]
    }
});
let Counter = connect(
    state=>state.counter
)(
  props=>(
      <div>
        <p>{props.number}</p>
        <button onClick={()=>props.dispatch({type:'counter/add'})}>+</button>
        <button onClick={()=>props.dispatch({type:'counter/asyncAdd'})}>异步加1</button>
        <button onClick={()=>props.dispatch({type:'counter/addWatcher'})}>addWatcher</button>
        <button onClick={()=>props.dispatch({type:'counter/goto',payload:'/'})}>跳转到/路径里</button>
      </div>
  )
);
const Home = props=><div>首页</div>;
app.router(({history})=>(
     <Router history={history}>
      <>
        <Route path="/" exact component={Home}/>
        <Route path="/counter" exact component={Counter}/>
      </>
      </Router>
));
app.start('#root');

8.2 dva\index.js #

src\dva\index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import {connect,Provider} from 'react-redux';
import createSagaMiddleware from 'redux-saga';
import * as effects from 'redux-saga/effects';
import createRootSaga from './createRootSaga';
import createReducers from './createReducers';
import {createHashHistory} from 'history';
import {routerMiddleware,connectRouter,ConnectedRouter} from 'connected-react-router';
import {prefix} from './utils';
export {connect}

export default function(opts){
    const app = {
        _models:[],//存放着所有的模型
        model,//添加模型的方法
        _router:null,//此处存放着路由定义
        router,//定义路由的方法
        start
    }
    function model(model){
        app._models.push(model);
    }
    function router(routerConfig){
        app._router = routerConfig;
    }
    function start(selector){
        let history = opts.history||createHashHistory();
        let rootReducer = createReducers(app._models,history,opts.extraReducers);//router
        let finalReducer = function(state,action){
            let newRootReducer = opts.onReducer(rootReducer);
            let newState = newRootReducer(state,action);
            if(opts.onStateChange){
                 opts.onStateChange(newState);
            }
            return newState;
        }
        let sagaMiddleware = createSagaMiddleware();
        let rootSaga = createRootSaga(app._models,opts.onError||function(){},opts.onEffect);
        if(opts.onAction){
            if(!Array.isArray(opts.onAction)){
                opts.onAction=[opts.onAction];
            }
        }else {
         opts.onAction=[]
        }
        let newCreateStore = createStore;
        if(opts.extraEnhancers){
            newCreateStore = opts.extraEnhancers(createStore);
        }
        let store = newCreateStore(finalReducer,opts.initialState||undefined,
        applyMiddleware(routerMiddleware(history),sagaMiddleware,...opts.onAction));
        sagaMiddleware.run(rootSaga);//开始启动rootSaga执行
        let App = app._router({history});
        ReactDOM.render(
            <Provider store={store}>
                <ConnectedRouter history={history}>
                    {App}
                </ConnectedRouter>
            </Provider>
            ,document.querySelector(selector));
    }
    return app;
}

8.3 router.js #

src\dva\router.js

module.exports = require('react-router-dom');
module.exports.routerRedux = require('connected-react-router');

8.4 createReducers.js #

src\dva\createReducers.js

import {combineReducers} from 'redux';
import {prefix} from './utils';
import {connectRouter} from 'connected-react-router';
export default function createReducers(models,history,extraReducers) {
  let reducers = {router:connectRouter(history),...extraReducers};
  for (let model of models) {
    let { namespace, state: initialState, reducers: modelReducers } = model;
    let reducersWithPrefix = prefix(modelReducers, namespace); //key已经增加了命名空间前缀了
    reducers[namespace] = function(state = initialState, action) {
      let reducer = reducersWithPrefix[action.type]; //type ='counter/add'
      if (reducer) {
        return reducer(state, action);
      } //如果reducer没有匹配上,说明这个action不是给我处理的,直接返回老状态
      return state; //总状态的counter 属性
    };
  }
  return  combineReducers(reducers);
}

8.5 createRootSaga.js #

src\dva\createRootSaga.js

import {prefix} from './utils';
import * as sagaEffects from 'redux-saga/effects';
export default function createRootSaga(models,onError,onEffect){
   function* rootSaga(){
       try{
         for(let model of models){
           model.effects = prefix(model.effects,model.namespace);
           for(let key in model.effects){//key counter/asyncAdd
              let effect = model.effects[key];
              if(Array.isArray(effect)&&effect[1].type == 'watcher'){
                  let effect = model.effects[key];
                  yield sagaEffects.fork(effect[0],sagaEffects);
              }else{
                yield sagaEffects.takeEvery(key,function*(action){
                    let oldEffect = model.effects[key];
                    let newEffect = onEffect(oldEffect,sagaEffects,model,action.type);
                    yield newEffect(action,sagaEffects);
                });
              }  
           }
       }
       }catch(error){
           onError(error);
       }

   }
   return rootSaga;
}

8.参考 #

  • dvajs
  • zhufeng-dva-source4

访问验证

请输入访问令牌

Token不正确,请重新输入