dva( 轻量级的应用框架 )

news2024/9/25 23:20:09

dva核心知识与实战运用

dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架!

介绍 | DvaJS

  • 易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API

  • elm 概念,通过 reducers, effects 和 subscriptions 组织 model

  • 插件机制,比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading

  • 支持 HMR,基于 babel-plugin-dva-hmr 实现 components、routes 和 models 的 HMR

1. 如何使用dva?

1.1 在create-react-app的基础上使用dva

在create-react-app脚手架的基础上,额外安装的内容:

  • 无需手动进行antd按需导入

  • 无需安装:redux及redux-saga、react-redux、react-router-dom等,dva把这些东西都集成好了,安装一个dva就相当于安装了这些全部东西!!

    • react-router-dom使用的是v4版本「4.3.1」

    • redux使用的是 v3.7.2「我们之前使用的都是v4.0」

    • 集成的配套插件版本有点低

    • 在React18的脚手架中使用dva会有警告错误!!

  • history 是控制路由模式的

  • 其余的按照之前讲的配置方案去配置webpack,包括:less、跨域代理、兼容、响应式布局等

 注意安装的版本

{
    "dependencies": {
        "antd": "^5.0.0",
        "antd-icons": "^0.1.0-alpha.1",
        "dva": "^2.4.1",
        "http-proxy-middleware": "^2.0.6",
        "less": "^4.1.3",
        "less-loader": "^8.1.1",
        "prop-types": "^15.8.1",
        "styled-components": "^5.3.6",
        "history": "4.10.1",
        ......
    }
}

项目的结构目录,可以依然沿用之前的命名风格:

  • api 接口管理和请求封装

  • assets 静态资源文件

  • router 路由统一配置

  • store redux公共状态管理

  • views 普通业务组件

  • components 公共业务组件

  • index.jsx 入口

  • setupProxy.js 跨域代理

但是有很多文件的编写方式和之前是不一样的!!


index.js入口

import dva from 'dva';
import createHistory from 'history/createHashHistory';
import RouterConfig from './router';
import voteModel from './store/voteModel';

// 初始化配置
const app = dva({
  // 设置路由模式{默认HASH路由}
  history: createHistory()
});
// 使用插件
app.use({});
// redux公共状态管理
app.model(voteModel);
// 路由配置
app.router(RouterConfig);
// 启动dva
app.start('#root');

router/index.js 配置页面入口和路由

import React from 'react';
import { Router, Route, Switch, Redirect } from 'dva/router';
import Vote from '../views/Vote';
import Demo from '../views/Demo';
/* ANTD */
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import '../assets/reset.min.css';

function RouterConfig({ history }) {
    return (
        <ConfigProvider locale={zhCN}>
            <Router history={history}>
                <Switch>
                    <Route path="/" exact component={Vote} />
                    <Route path="/demo" component={Demo} />
                    <Redirect to="/" />
                </Switch>
            </Router>
        </ConfigProvider>
    );
}
export default RouterConfig;

store/voteModel.js 配置每个模块的Model,包含:状态、reducer、异步派发的方法等

import _ from '../assets/utils';
const delay = (interval = 1000) => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, interval);
    });
};
export default {
    namespace: 'vote',
    state: {
        supNum: 10,
        oppNum: 5
    },
    reducers: {
        support(state, action) {
            state = _.clone(true, state);
            let { payload = 1 } = action;
            state.supNum += payload;
            return state;
        },
        oppose(state, action) {
            state = _.clone(true, state);
            let { payload = 1 } = action;
            state.oppNum += payload;
            return state;
        }
    },
    effects: {
        supportAsync: [
            function* ({ payload }, { call, put }) {
                yield call(delay, 2000);
                yield put({
                    type: 'support',
                    payload
                });
            },
            { type: 'takeLatest' }
        ],
        *opposeAsync({ payload }, { call, put }) {
            yield call(delay, 2000);
            yield put({
                type: 'oppose',
                payload
            });
        }
    }
};

在组件中如何使用呢?

import React from "react";
import styled from "styled-components";
import { Button } from 'antd';
import { connect } from 'dva';

// 样式处理
const VoteBox = styled.div`
    ...
`;

const Vote = function Vote(props) {
    let { supNum, oppNum, dispatch } = props;
    return <VoteBox>
        <div className="header">
            <h2 className="title">React是很棒的前端框架</h2>
            <span className="num">{supNum + oppNum}</span>
        </div>
        <div className="main">
            <p>支持人数:{supNum}人</p>
            <p>反对人数:{oppNum}人</p>
        </div>
        <div className="footer">
            <Button type="primary"
                onClick={() => {
                    dispatch({
                        type: 'vote/supportAsync',
                        payload: 10
                    });
                }}>
                支持
            </Button>
            <Button type="primary" danger
                onClick={() => {
                    dispatch({
                        type: 'vote/opposeAsync'
                    });
                }}>
                反对
            </Button>
        </div>
    </VoteBox>;
};
export default connect(state => state.vote)(Vote);

1.2 但是更多的时候,我们会直接使用 dva 自带的脚手架创建项目 

dva脚手架创建的项目是基于 roadhog /rəʊd hog/ 进行webpack的配置!!
roadhog是一个cli工具,提供server、 build和test三个命令,分别用于本地调试和构建,并且提供了特别易用的mock功能。命令行体验和create-react-app一致,配置略有不同,比如默认开启 css modules,然后还提供了JSON格式的配置方式!
$ npm install dva-cli -g
$ dva -v
$ dva new my-project

 

 package.json

{
  "private": true,
  "scripts": {
    "start": "cross-env PORT=3000 HOST=127.0.0.1 roadhog server", //开发环境启动
    "build": "roadhog build", //生产环境打包
    "lint": "eslint --ext .js src test", //单元测试
    "precommit": "npm run lint"
  },
  "dependencies": {
    "@babel/polyfill": "^7.12.1",
    "antd": "4.24.7", //注意版本用v4「不是最新的v5」
    "antd-icons": "^0.1.0-alpha.1",
    "babel-plugin-import": "^1.13.5", //antd按需导入
    "dva": "^2.4.1",
    "history": "4.10.1", //管理路由模式的「用v4不是最新的v5版本」
    "lib-flexible": "^0.3.2",
    "postcss-pxtorem": "5.1.1",
    "prop-types": "^15.8.1",
    "qs": "^6.11.0",
    "react": "^16.2.0", //react使用的是v16版本
    "react-dom": "^16.2.0",
    "styled-components": "^5.3.6"
  },
  "devDependencies": {
    "babel-plugin-dva-hmr": "^0.3.2", //热更新
    "cross-env": "^7.0.3",
    "less": "4.1.3",
    "less-loader": "8.1.1",
    ...
  }
}

 修改webpack配置项

修改启动的域名和端口号:设置环境变量即可

  • PORT

  • HOST

  • HTTPS 是否开启https,默认关闭

  • BROWSER 设为none时不自动打开浏览器

  • CLEAR_CONSOLE 设为none时清屏

“start”: “cross-env PORT=3000 HOST=127.0.0.1 roadhog server”,

.webpackrc改为.webpackrc.js,这样就可以按照JS方式去编写配置项了!!

  • 修改入口、出口、打包配置等

  • Antd按需导入

  • 配置跨域代理

  • 配置响应式布局方案

  • 配置less

  • 不同环境下的配置

  • 浏览器兼容

  • ……

PC配置:

import px2rem from 'postcss-pxtorem';
export default {
    /* 基础配置 */
    "entry": "src/index.js", //配置多入口:src/enter/*.js
    "outputPath": "./dist",
    "publicPath": "/",
    "hash": true,
    "html": {
        "template": "./public/index.ejs"
    },
    /* 配置LESS */
    "disableCSSModules": true,
    /* 配置PX转REM */
    "extraPostCSSPlugins": [
        px2rem({
            "rootValue": 75,
            "propList": ['*']
        })
    ],
    /* 配置BABEL的插件 */
    "extraBabelPlugins": [
        // antd按需导入
        [
            "import",
            {
                "libraryName": "antd",
                "libraryDirectory": "es",
                "style": "css"
            }
        ],
        // 配置PX转REM
        [
            "styled-components-px2rem",
            {
                "rootValue": 75
            }
        ]
    ],
    /* 配置跨域代理 */
    "proxy": {
        "/api": {
            "target": "https://news-at.zhihu.com/api/4",
            "changeOrigin": true,
            "ws": true,
            "pathRewrite": {
                "/api": ""
            }
        }
    },
    /* 不同环境下的不同配置 */
    "env": {
        "development": {
            "extraBabelPlugins": [
                "dva-hmr"
            ]
        }
    }
};

 浏览器兼容:默认情况下,ES6语法和CSS3的兼容已经处理,如果想处理ES6内置API的兼容,则导入@babel/polyfill即可「入口导入」!!

移动配置:

import px2rem from 'postcss-pxtorem';
export default {
    // 对于css的处理 
    disableCSSModules: true,
    disableCSSSourceMap: true,
    /* 基础配置 */
    "entry": "src/index.js", //配置多入口:src/enter/*.js
    "outputPath": "./dist",
    "publicPath": "/",
    "hash": true,
    // "html": {
    //     "template": "./public/index.ejs"
    // },
    /* 配置LESS */
    "disableCSSModules": true,
    /* 配置PX转REM */
    "extraPostCSSPlugins": [
        px2rem({
            "rootValue": 75,
            "propList": ['*']
        })
    ],
    /* 配置BABEL的插件 */
    "extraBabelPlugins": [
        // antd按需导入
        [
            "import",
            {
                "libraryName": "antd",
                "libraryDirectory": "es",
                "style": "css"
            }
        ],
        // 配置PX转REM
        [
            "styled-components-px2rem",
            {
                "rootValue": 75
            }
        ]
    ],
    /* 配置跨域代理 */
    "proxy": {
        "/api": {
            "target": "https://localhost:8888",
            "changeOrigin": true,
            "ws": true,
            "pathRewrite": {
                "/api": ""
            }
        }
    },
    /* 不同环境下的不同配置 */
    "env": {
        "development": {
            "extraBabelPlugins": [
                "dva-hmr"
            ]
        }
    }
};

2. dva中的路由配置

index.js

import dva from 'dva';
/*
 安装history模块「安装v4.10.1版本,不建议安装最新版本」
 $ yarn add history@4.10.1
 默认开启的就是HASH路由,如果想使用History路由,则导入createBrowserHistory!!
*/
import createHistory from 'history/createHashHistory';
    const app = dva({
        // 指定路由模式
        history: createHistory()
    });
...
app.router(require('./router').default);
app.start('#root');

router.js

/* 
dva/router中包含了react-router-dom v5版本中所有API,以及react-router-redux中的的API 
*/
import React from 'react';
import { Router, Route, Switch, Redirect } from 'dva/router';
import Vote from './routes/Vote';
import Demo from './routes/Demo';
import Personal from './routes/Personal';

/* ANTD */
...

const RouterConfig = function RouterConfig({ history }) {
  return <ConfigProvider locale={zhCN}>
    <Router history={history}>
      <Switch>
        <Route path="/" exact component={Vote} />
        <Route path="/demo" component={Demo} />
        <Route path="/personal" component={Personal} />
        <Redirect to="/" />
      </Switch>
    </Router>
  </ConfigProvider>;
}
export default RouterConfig;

路由懒加载

路由懒加载主要使用 dva下的dynamic

API | DvaJS

import React from 'react';
import { Router, Route, Switch, Redirect } from 'dva/router';
import Vote from './routes/Vote';
import dynamic from 'dva/dynamic'; //实现动态组件的API

/* ANTD */
...

const RouterConfig = function RouterConfig({ history, app }) {
  /* 异步组件 */
  const DemoAsync = dynamic({
    app,
    models: () => [
      import(/* webpackChunkName:"demo" */ './models/demoModel')
    ],
    component: () => import(/* webpackChunkName:"demo" */ './routes/Demo')
  });
  const PersonalAsync = dynamic({
    app,
    models: () => [
      import(/* webpackChunkName:"personal" */ './models/personalModel')
    ],
    component: () => import(/* webpackChunkName:"personal" */ './routes/Personal')
  });

  return <ConfigProvider locale={zhCN}>
    <Router history={history}>
      <Switch>
        <Route path="/" exact component={Vote} />
        <Route path="/demo" component={DemoAsync} />
        <Route path="/personal" component={PersonalAsync} />
        <Redirect to="/" />
      </Switch>
    </Router>
  </ConfigProvider>;
}
export default RouterConfig;

配置路由表和二级路由

routerRoutes.js 路由表

import Vote from './routes/Vote';
import dynamic from 'dva/dynamic';
/* 配置路由懒加载 */
const lazy = function lazy(models, component) {
    return dynamic({
        app: window.app, //在入口处挂载到window上
        models,
        component
    });
};

const routes = [{
    path: '/',
    exact: true,
    component: Vote,
    meta: { title: '首页' }
}, {
    path: '/demo',
    component: lazy(
        () => [import(/* webpackChunkName:"demo" */ './models/demoModel')],
        () => import(/* webpackChunkName:"demo" */ './routes/Demo')
    ),
    meta: { title: '测试页' }
}, {
    path: '/personal',
    component: lazy(
        () => [import(/* webpackChunkName:"personal" */ './models/personalModel')],
        () => import(/* webpackChunkName:"personal" */ './routes/Personal')
    ),
    meta: { title: '个人中心' },
    /* 二级路由 */
    children: [{
        redirect: true,
        exact: true,
        from: '/personal',
        to: '/personal/order'
    }, {
        path: '/personal/order',
        component: lazy(
            () => [],
            () => import(/* webpackChunkName:"personal" */ './routes/personal/MyOrder')
        ),
        meta: { title: '个人中心-我的订单' }
    }, {
        path: '/personal/profile',
        component: lazy(
            () => [],
            () => import(/* webpackChunkName:"personal" */ './routes/personal/MyProfile')
        ),
        meta: { title: '个人中心-我的信息' }
    }]
}, {
    redirect: true,
    to: '/'
}];
export default routes;

router.js

import React from 'react';
import { Router, Route, Switch, Redirect } from 'dva/router';
import routes from './routerRoutes';
/* ANTD */
...
/* 动态创建路由 */
const createRoute = function createRoute(routes) {
  return <Switch>
    {routes.map((item, index) => {
      let { redirect, from, to, exact, path, meta, component: Component } = item,
        config = {};
      // 重定向
      if (redirect) {
        config = { to };
        if (from) config.from = from;
        if (exact) config.exact = exact;
        return <Redirect {...config} key={index} />;
      }
      // 正常路由
      config = { path };
      if (exact) config.exact = exact;
      return <Route {...config} key={index}
        render={(props) => {
          // 修改标题
          let { title = '' } = meta;
          document.title = `${title}-珠峰培训React`;
          return <Component {...props} />;
        }} />;
    })}
  </Switch>;
};
/* 一级路由 */
const RouterConfig = function RouterConfig({ history }) {
  return <ConfigProvider locale={zhCN}>
    <Router history={history}>
      {createRoute(routes)}
    </Router>
  </ConfigProvider>;
};
/* 二级路由 */
export const childrenRouter = function childrenRouter(path) {
  let item = routes.find(item => item.path === path),
    children;
  if (item) children = item.children;
  if (!children) return null;
  return createRoute(children);
};
export default RouterConfig;

index.js

import dva from 'dva';
import createHistory from 'history/createHashHistory';
import voteModel from './models/voteModel';
// 1. Initialize
const app = dva({
    history: createHistory()
});
window.app = app;
// 2. Plugins
// app.use({});
// 3. Model
app.model(voteModel);
// 4. Router
app.router(require('./router').default);
// 5. Start
app.start('#root');

Personal.jsx

import React from "react";
import { NavLink } from 'dva/router';
import styled from "styled-components";
import { childrenRouter } from '../router';
/* 样式处理 */
const PersonalBox = styled.div`
    ...
`;
const Personal = function Personal() {
    return <PersonalBox>
        <div className="menu">
            <NavLink to="/personal/order">我的订单</NavLink>
            <NavLink to="/personal/profile">我的信息</NavLink>
        </div>
        <div className="content">
            {childrenRouter('/personal')}
        </div>
    </PersonalBox>;
};
export default Personal;

路由跳转及传参

     history对象中提供了路由跳转的方法

       + go

       + goBack -> go(-1)

       + goFoward -> go(1)

       + push

       + replace

路径参数:把传递的信息当做路由地址的一部分,但是需要路由地址基于”:?“设置匹配的规则
路由地址:'/personal/profile/:lx?/:name?',
history.push(`/personal/profile/0/zhufeng`); 
 
问号传参:传递的信息会存在于地址栏中,即便用户刷新页面,依然可以获取相关传递的信息
history.push({
    pathname: '/personal/profile',
    search: 'lx=0&name=zhufeng'
}); 
 
隐式传参:基于state把信息传递给目标组件,但是传递的信息没有在地址中存在「不丑+安全」,这样在目标组件页面刷新,传递的信息就消失了!!
history.push({
    pathname: '/personal/profile',
    state: {
        lx: 0,
        name: 'zhufeng'
    }
});

方案一:Link 和 NavLink
NavLink可以和路由地址进行匹配,设置选中样式!!

<div className="menu">
    <NavLink to="/personal/order">我的订单</NavLink>
    <NavLink to="/personal/profile">我的信息</NavLink>
</div>

方案二:编程式导航

 routerRedux 是 react-router-redux 中提供的对象,此对象中包含了路由跳转的方法
   + go/goBack/goFoward
   + push/replace
 相比较于props.history对象来讲,routerRedux不仅可以在组件中实现路由跳转,而且可以在redux操作中实现路由的跳转!!它本身就是redux和router的结合操作!!

   在redux内部
     yield put(routerRedux.push(...))
   在redux外部「或者组件中」
     dispatch(
        routerRedux.push(...)
     )
     一定要基于dispatch进行派发才会跳转;因为执行routerRedux.xxx方法,只会返回一个action对象;
     action->{
        type:"@@router/CALL_HISTORY_METHOD",
        payload:{
            method:'push', 
            args:[...] 
        }
     }

import React from "react";
import { routerRedux } from 'dva/router';
import { connect } from 'dva';

const MyOrder = function MyOrder(props) {
     
    基于路由匹配的组件,其属性中包含:history、location、match!
      其中history就是实现路由跳转的
        + push
        + replace
        + go
        + goBack
        + goForward
      如果组件不是基于路由匹配的,可以基于 withRouter 高阶函数处理即可!!
    
    let { history, dispatch } = props;
    return <div className="myOrderBox">
        我的订单
        <button onClick={() => {
            // history.push('/personal/profile');

             
             routerRedux 也可以实现路由跳转,语法和history类似
             好处:可以在Effects中基于 yield 实现路由跳转
                // Inside Effects
                yield put(routerRedux.push('/logout'));

                // Outside Effects
                dispatch(routerRedux.push('/logout'));
             
            dispatch(routerRedux.push('/personal/profile'));
        }}>跳转</button>
    </div>;
};
export default connect()(MyOrder);

3. dva中Model处理 


model处理流程

1. 如果有引入多个model ,app.model可以多次执行  这样会降低首屏的加载速度

import voteModel from './models/vote';
app.model(voteModel);  
import voteModel from './models/vote2';
app.model(voteModel2);  

2. 页面需要的时候懒加载,配合路由使用懒加载 dynamic

3. Model的组成

  1. namespace 命名空间【模块名,后期获取状态和派发的标识】
  2. state 数据 【模块管理的公共状态】

  3. reducers 同步处理的方法 【已一个一个方法的模式,完成reducer中的派发行为标识的判断以及状态的更改+同步修改+外部修改+外部派发dispatch('/demo/xxx')】

  4. effects   redux-saga中异步处理方法【实现异步操作,异步派发】

  5. subscriptions  订阅【在这里订阅的方法,会在页面一加载的时候就会被通知执行,所以:我们把页面一加载就要做的事情 (和 redux 相关的)在这里处理,在这里我们可以基于 history.listen做监听,保证进入哪个组件再处理也可以】

4. 在组件中,可以基于 dva中提供的 connect高阶函数,使用公共状态及dispatch方法

入口

import voteModel from './models/voteModel';
...
app.model(voteModel);
...

基本结构

export default {
    // 命名空间「模块名:后期获取状态和派发都需要这个名字」
    namespace: 'vote',
    // 此模块管理的公共状态
    state: {},
    // 此模块需要判断的reducer「同步派发直达reducers」
    reducers: {},
    // 此模块需要异步派发的任务「基于redux-saga语法处理」
    effects: {},
    // 订阅方法,一开始就自动执行「获取数据,实现派发等」
    subscriptions: {}
};

实现计数器累计

Demo.jsx

import React from "react";
import styled from "styled-components";
import { connect } from 'dva'
import { Button } from 'antd';
...
const Demo = function Demo(props) {
    let { num, dispatch } = props;
    return <DemoBox>
        <span className="num">{num}</span>
        <Button type="primary"
            onClick={() => {
                dispatch({
                    type: "demo/increment",
                    payload: 5
                });
            }}>
            按钮
        </Button>
        <Button type="primary" danger
            onClick={() => {
                dispatch({
                    type: 'demo/incrementAsync',
                    payload: 10
                });
            }}>
            异步按钮
        </Button>
    </DemoBox>;
};
export default connect(state => state.demo)(Demo);

demoModel.js

import _ from '../utils/utils';
const delay = (interval = 1000) => {
    ...
};
export default {
    namespace: 'demo',
    state: {
        num: 0
    },
    reducers: {
        increment(state, action) {
            state = _.clone(true, state);
            let { payload = 1 } = action;
            state.num += payload;
            return state;
        }
    },
    effects: {
        *incrementAsync({ payload }, { call, put }) {
            yield call(delay, 2000);
            yield put({
                type: 'increment',
                payload
            });
        }
    }
};

effects中的特殊处理

effects: {
    incrementAsync: [
        function* ({ payload }, { call, put, select }) {
            try {
                // 获取状态
                let { num } = yield select(state => state.demo);
                // 发送请求
                yield call(delay, 2000);
                // 派发任务
                yield put({
                    type: 'increment',
                    payload
                });
            } catch (err) {
                // 异常捕获
                console.log(err);
            }
        },
        // 指定监听的类型,默认是takeEvery「还有:takeLatest、throttle等」
        { type: "takeLatest" },
        // { type: "throttle", ms: 1000 }
    ]
}

subscriptions

app.model({
    subscriptions: {
        setup({ dispatch, history }) {
            history.listen(location => {
                if (location.pathname === '/demo') {
                    dispatch({
                        type: 'demo/increment',
                        payload: 100
                    });
                }
            });
        }
    }
})

懒加载的model

const delay = (interval = 1000) => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, interval);
    });
};
export default {
    namespace: 'demo',
    state: {
        num: 10
    },
    reducers: {
         
         把原有reducer函数中的每一种switch/case情况都写成一个单独的方法「纯函数」
           state:获取“本模块”的公共状态
           action:派发时候传递的action对象「包含type和传递的其他值(一般基于payload字段传递)」
           我们需要把获取的state克隆一份,然后函数最后返回的值,会替换当前模块的state!!
         
        increment(state, { payload = 1 }) {
            /* state = { ...state };
            state.num += payload;
            return state; */
            return {
                ...state,
                num: state.num + payload
            };
        }
    },
    effects: {
         
         redux-saga中我们基于take/takLatest/takeEvery等方式创建的监听器,此时写成一个个的“Generator函数”即可!!-> 默认是基于takeEvery的方式创建的监听器
           + 方法名是我们创建的监听器名字
           + 方法就是派发的任务被监听后,执行的working方法
           + 此处的函数名,不要和reducers中的函数名一致,因为:每一次派发,reducers和effects中的方法都会去匹配执行!如果函数名一样,则状态修改两次!!我们一般在effects写的名字,都加Async!!
         
         方法中的参数
           + action:在组件中进行派发时,传递的action对象
           + 第二个参数就是redux-saga中提供的EffectsAPI,但是没有delay/debounce...
             + 基于 yield select() 可以获取所有模块的公共状态
               yield select(state=>state.demo) 这样就是获取指定的状态信息
         
        *incrementAsync({ payload }, { call, put }) {
            yield call(delay, 2000);
            yield put({
                type: 'increment',
                payload
            });
        }
         如果想设置不同类型的监听器,则这样写
        /* incrementAsync: [
            // 数组第一项是working函数
            function* ({ payload }, { call, put }) {
                yield call(delay, 2000);
                yield put({
                    type: 'increment',
                    payload
                });
            },
            // 数组第二项中指定监听器的类型
            { type: 'takeLatest' }
            // { type: 'throttle', ms: 500 }
        ] */
    },
  
     demoModel是被懒加载的,只有访问了/demo这个地址(组件),demoModel才会被注册!!
       这里订阅的方法
       + 只有进入到这个组件,Model懒加载完毕,也被注册了,subscriptions中订阅的方法才会被执行
       + 而且只会执行一次,后期路由来回切换的时候,也不再执行了
   
    subscriptions: {
        setup() { },
      

    }
};

加载页面就注册

     这个板块的Model是加载页面时就被立即注册的 
       + subscriptions中写的方法,在页面一加载的时候,就会把所有设定的方法执行
       + 方法就是普通函数「不能是Generator函数」
         + 传递的实参对象中具备 history/dispatch 两个属性
         + history:包含路由跳转和监听的history对象
         + dispatch:进行派发的方法
       + 如果想页面一加载「或者是指定的某个条件下」,我们就想从服务器异步获取数据,修改此模块的状态值,则可以写在subscriptions中!!
     
    subscriptions: {
        // 方法只有页面一加载的时候,订阅执行一次,在后期路由切换中,不再执行
        /* async setup({ history, dispatch }) {
            console.log('VOTE-SETUP');
            await delay(2000);
            dispatch({
                type: 'support'
            });
        } */

         需求改变了一下:我们想的是,在页面第一次/重新加载的时候,只有进入Vote这个组件,我们在voteModel中写的setup,以及其内部的操作,才让其生效!!
        setup({ history, dispatch }) {
            // 在Model没有懒加载的情况下,我们可以让setup函数在页面第一次加载的过程中,就订阅到事件池里,并且通知执行!!我们在setup中基于history.listen创建路由跳转监听器:第一次会执行,以后每一次路由切换也会执行!!
            let unlisten = history.listen(async (location) => {
                let { pathname } = location;
                if (pathname === '/') {
                    await delay(2000);
                    dispatch({
                        type: 'support'
                    });
                    // 返回的函数就是移除此监听器的操作
                    unlisten();
                }
            });
        }
    }

4. dva-loading插件的应用


dva-loading 会监听指定的异步请求方法,方法开始时loading状态值为 true ,异步结束后该值自动置为 false , 可用于骨架屏或某些需要 loading 状态的场景!
$ yarn add dva-loading


使用方式:

  1. npm or yarn 安装dva-loading ,并在入口Index.js中引入, 示例:import createLoading from 'dva-loading';
  2. 在入口Index.js中 app.use( createLoading ) ,示例:app.use(createLoading());
  3. 在组件context高阶函数中 state可以拿到loading ,示例:state => {
            return {
                ...state.demo,
                loading: state.loading
            };
        }
  4. 组件内指定loading对应的 effects  示例:【loading = loading.effects['demo/testAsync'];】 

打印loading:

 index.js

import createLoading from 'dva-loading';
...
app.use(createLoading());
...

models/demoModel.js

const delay = (interval = 1000) => {
    ...
};
export default {
    namespace: 'demo',
    state: {
        num: 0
    },
    reducers: {
        test(state) {
            state = { ...state };
            state.num++;
            return state;
        }
    },
    effects: {
        *testAsync(action, { call, put }) {
            yield call(delay, 2000);
            yield put({
                type: 'test'
            });
        }
    }
};

组件中使用

import { connect } from "dva";
...
const Demo = function Demo({ num, loading, dispatch }) {
    loading = loading.effects['demo/testAsync'];
    return <DemoBox>
        <span className="num">{num}</span>
        <Button type="primary" danger
            loading={loading}
            onClick={() => {
                dispatch({ type: 'demo/testAsync' });
            }}>
            异步按钮
        </Button>
    </DemoBox>;
};
export default connect(
    state => {
        return {
            ...state.demo,
            loading: state.loading
        };
    }
)(Demo);

中间件:

npm view xxx version 查看历史版本

这里使用 2.10.2 版本

Redux Middleware

5. 基于dva重写投票案例


voteModel.js

import _ from '../utils/utils';
const delay = (interval = 1000) => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, interval);
    });
};

export default {
    namespace: 'vote',
    state: {
        supNum: 10,
        oppNum: 5
    },
    reducers: {
        support(state, action) {
            state = _.clone(true, state);
            let { payload = 1 } = action;
            state.supNum += payload;
            return state;
        },
        oppose(state, action) {
            state = _.clone(true, state);
            let { payload = 1 } = action;
            state.oppNum += payload;
            return state;
        }
    },
    effects: {
        supportAsync: [
            function* ({ payload }, { call, put }) {
                yield call(delay, 2000);
                yield put({
                    type: 'support',
                    payload
                });
            },
            { type: 'takeLatest' }
        ],
        opposeAsync: [
            function* opposeAsync({ payload }, { call, put }) {
                yield call(delay, 2000);
                yield put({
                    type: 'oppose',
                    payload
                });
            },
            { type: 'takeLatest' }
        ]
    }
};

Vote.jsx

import React from "react";
import styled from "styled-components";
import { Button } from 'antd';
import { connect } from 'dva';
...
const Vote = function Vote(props) {
    let { supNum, oppNum, dispatch } = props;
    return <VoteBox>
        <div className="header">
            <h2 className="title">React是很棒的前端框架</h2>
            <span className="num">{supNum + oppNum}</span>
        </div>
        <div className="main">
            <p>支持人数:{supNum}人</p>
            <p>反对人数:{oppNum}人</p>
        </div>
        <div className="footer">
            <Button type="primary"
                onClick={() => {
                    dispatch({
                        type: 'vote/supportAsync',
                        payload: 10
                    });
                }}>
                支持
            </Button>
            <Button type="primary" danger
                onClick={() => {
                    dispatch({
                        type: 'vote/opposeAsync'
                    });
                }}>
                反对
            </Button>
        </div>
    </VoteBox>;
};
export default connect(state => state.vote)(Vote);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/382284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

易基因-MeRIP-seq揭示衰老和神经变性过程中m6A RNA甲基化修饰的保守下调机制

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。2023年02月22日&#xff0c;《美国国家科学院院刊》(Proc Natl Acad Sci USA)期刊发表了题为“Conserved reduction of m6A RNA modifications during aging and neurodegeneration is lin…

机器学习:学习KMeans算法,了解模型创建、使用模型及模型评价

机器学习&#xff1a;学习KMeans算法&#xff0c;了解模型创建、使用模型及模型评价 作者&#xff1a;AOAIYI 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;AOAIYI首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#…

android 动态加载jar包

什么时候需要用到动态加载jar包呢&#xff1f; 减少apk本身大小&#xff0c;某些界面才需要用jar里的功能jar中的功能可以独立开发&#xff0c;不要依赖其它的东西&#xff0c;能适用各个项目和第三方公司合作&#xff0c;人家不提供项目源码给你&#xff0c;但又要开发他们使…

MySQL workbench的基本操作

1. 创建新的连接 hostname主机名输入“local host”和“127.0.0.1”效果是一样的&#xff0c;指的是本地的服务器。 需要注意的是&#xff0c;此处的密码在安装软件的时候已经设定。 点击【Test Connection】&#xff0c;测试连接是否成功。 创建完的连接可以通过&#xff0c…

汇编指令学习(ADD,SUB,MUL,DIV,XADD,INC,DEC,NEG)

一、ADD加法操作指令将eax置1&#xff0c;ebx置2&#xff0c;运行下面命令&#xff0c;将结果保存到eaxadd eax,ebx扩展&#xff1a;adc需要再加上CF标志位的值adc eax&#xff0c;ebx二、SUB减法操作指令将eax置3&#xff0c;ebx置2&#xff0c;运行下面命令&#xff0c;将结果…

java异常分类和finally代码块中return语句的影响

首先看一下java中异常相关类的继承关系&#xff1a; 引用 1、分类 异常可以分为受查异常和非受查异常&#xff0c;Error和RuntimeException及其所有的子类都是非受查异常&#xff0c;其他的是受查异常。 两者的区别主要在&#xff1a; 受检的异常是由编译器&#xff08;编译…

CUDA环境搭建(windows10)

CUDA环境搭建[windows10]一、检查显卡支持的cuda版本二、安装vs2019三、安装cuda四、检测cuda是否安装成功五、配置vs项目总结&#xff1a;一、检查显卡支持的cuda版本 &#xff08;1&#xff09;第一种方法&#xff1a;winR打开cmd&#xff0c;输入nvidia-smi&#xff0c;我的…

常用的shell脚步操作

文章目录一、如何开始一个shell脚本?1.基本语法2.变量定义规则二、特色变量1.$n2.$&#xff1f;三、条件判断1&#xff0e;基本语法2.运算符if,for,while四、字符串切割1.从指定位置开始截取从字符串左边开始计数从右边开始计数2.从指定字符&#xff08;子字符串&#xff09;开…

【MySQL】查询操作(基础篇)

目录 1、查询操作(Retrieve) 1.1 全列查询 1.2 指定列查询 1.3 查询字段为表达式 1.4 别名 1.5 去重&#xff1a;DISTINCT 1.6 排序&#xff1a;ORDER BY 1.7 条件查询&#xff1a;WHERE 1.8 分页查询 1、查询操作(Retrieve) 查询操作算的上是 SQL 中最复杂的操作了…

Orcad导出BOM的两种方式比较

两种方法&#xff0c;各有优缺点。推荐使用第二种。一、Capture CIS特有的导BOM方式&#xff0c;用Capture 就没有这个选项点击Stanard,然后可以在左框中select ,add进右边的框&#xff0c;这样导就会导出你想要的属性&#xff0c;同时右下方有个Key选项&#xff0c;你选择outp…

适用于电脑的 5 款免费好用的 PDF 阅读器

PDF 阅读器是任何可以打开PDF 文件的软件&#xff0c;它可能是世界上最知名的文档格式。您可下载的银行对账单、学校的时事通讯——它们可能都是 PDF 格式的。 越来越多的操作系统、智能手机和其他设备包含显示 PDF 文件的内置功能&#xff1b;甚至您的网络浏览器也可能处理它…

Vue2和Vue3响应式的区别

数据响应式是什么&#xff1f; ​所谓 数据响应式 就是建立 响应式数据 与 依赖&#xff08;调用了响应式数据的操作&#xff09;之间的关系&#xff0c;当响应式数据发生变化时&#xff0c;可以通知那些使用了这些响应式数据的依赖操作进行相关更新操作&#xff0c;可以是DOM…

基于ubuntu的STM32嵌入式软件开发(三)——基于官方标准函数库的软件工程移植

本文基于st官方提供的标准库搭建应用软件工程&#xff08;即非cube方式、非寄存器方式&#xff09;&#xff0c;采用标准库搭建的工程具有软件可移植性高、可读性好、符合软件设计人员思维方式。本文描述官方下载标准库及标准库的移植过程&#xff0c;具体流程如下所述&#xf…

不妙,2023年浙大mpa的复试形势比想象的更严峻,又在突突突涨……

都知道浙大MPA项目卷&#xff0c;但都没想到这么卷&#xff01; 很多浙大mpa的考生在一开始的备考初期因为对该项目的认知了解不够清晰从而会导致联考过后功亏一篑。因为是在职类考生群体&#xff0c;因此不少考生会认为以自己的实力和基础应该能够鱼跃龙门榜上有名&#xff0c…

视频会议系统异常中断故障分析案例

1. 背景 某电气化局的用户反馈&#xff0c;近期视频系统在使用过程中出现频繁中断的情况&#xff0c;这种情况影响到用户的视频体验和工作效率。 针对此问题&#xff0c;我们将NetInside流量分析系统部署到电气化局机房&#xff0c;使用流量分析系统提供实时和历史原始流量。…

【Linux】P1 Linux 基础命令(1)

Linux 基础命令&#xff08;1&#xff09;Linux 目录结构Linux 命令ls 展示命令cd 目录切换命令pwd 查看当前工作目录mkdir 创建新的文件夹其他补充知识前言 本节内容&#xff1a;Linux 基本命令&#xff08;1&#xff09;。 下节内容&#xff1a;Linux 基本命令&#xff08;2&…

二进制与十进制转换(包括整数和小数的转换)

二进制与十进制转换&#xff08;包括整数和小数的转换&#xff09; 二进制转十进制 首先要先了解二进制的含义&#xff0c;与十进制相似&#xff0c;二进制代表的是以2的次幂在每一位上的0/1表示&#xff0c;平时我们经常接触的都是整数的二进制&#xff0c;是从2的0次幂开始的…

从WebRtc学习RTP协议

1、TCP为何不适用于实时音视频可靠性是以牺牲实时性为代价的。按照TCP原理&#xff0c;当出现极端网络情况时&#xff0c;理论上每个包的时延可达到秒级以上&#xff0c;而且这种时延是不断叠加的。这对于音视频实时通信来说是不可接受的。TCP为了实现数据传输的可靠性&#xf…

【2223sW2】LOG1

写在前面 好好学习&#xff0c;走出宿舍&#xff0c;走向毕设&#xff01; 一些心路历程记录&#xff0c;很少有代码出现 因为鬼知道哪条代码到时候变成毕设的一部分了咧&#xff0c;还是不要给自己的查重挖坑罢了 23.2.27 文件批量重命名 为了给学姐先整出来一批训练数据&…

Element中树形控件在项目中的实际应用

文章目录1、使用目的2、官网组件3、组合使用组件案例4、在项目中实际应用4.1 组合组件的使用4.1.2 代码落地4.1.3 后台接口数据4.1.4 实际效果官网连接直达&#xff1a;Tree树形控件的使用 1、使用目的 用清晰的层级结构展示信息&#xff0c;可展开或折叠。 2、官网组件 <…