当前位置: 首页 > news >正文

dva( 轻量级的应用框架 )

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" dangeronClick={() => {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 Effectsyield put(routerRedux.push('/logout'));// Outside Effectsdispatch(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" dangeronClick={() => {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" dangerloading={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" dangeronClick={() => {dispatch({type: 'vote/opposeAsync'});}}>反对</Button></div></VoteBox>;
};
export default connect(state => state.vote)(Vote);

相关文章:

dva( 轻量级的应用框架 )

dva核心知识与实战运用 dva 首先是一个基于 redux 和 redux-saga 的数据流方案&#xff0c;然后为了简化开发体验&#xff0c;dva 还额外内置了 react-router 和 fetch&#xff0c;所以也可以理解为一个轻量级的应用框架! 介绍 | DvaJS 易学易用&#xff0c;仅有 6 个 api&…...

数据结构:堆的实现与建堆时间复杂度分析

目录 前言 一.堆的介绍 1.堆的本质 2.堆的分类 二.堆的实现(以小根堆为例) 1.关于二叉树的两组重要结论&#xff1a; 2.堆的物理存储结构框架(动态数组的简单构建) 3. 堆元素插入接口(以小根堆为例) 堆尾元素向上调整的算法接口: 4.堆元素插入接口测试 5.堆元素插入…...

对“车辆销售配置器”的认识与理解

概述 中国汽车市场转为存量阶段后&#xff0c;各车企开始从”以产品为中心“转型到”以客户为中心“&#xff0c;产品的个性化配置需求日益丰富。随着竞争的加剧&#xff0c;车企们不仅要提供出色的产品&#xff0c;而且需要提供更加个性化的产品配置和服务&#xff0c;例如&am…...

Linux编译器——gcc/g++(预处理、编译、汇编、链接)

目录 0.程序实现的两大环境 1.gcc如何完成 预处理 编译 汇编 链接 2.动态库与静态库 对比二者生成的文件大小 3. gcc常用选项 0.程序实现的两大环境 任何一个C程序的实现都要经过翻译环境与执行环境。 在翻译环境中又分为4个部分&#xff0c;预编译、编译、汇编与链…...

Java 操作图片进行缩放旋转翻转加水印

1 纯原生手写图片操作工具类 import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; public class RotateImageUtil {public static BufferedImage rotateImage(BufferedImage bu…...

不能去演唱会现场就多听听耳机里的他们,教你用python来实现一个音乐播放器

前言 最近可以说大麦网很知名了&#xff0c;哈哈还有好多想要用Python来搞抢票脚本的 怎么说呢也不是不行&#xff0c;但是咱今天可不是来搞这个的&#xff0c;我可不抢票&#xff0c;抢了都去不了&#xff0c;上班搞钱啊铁铁们 咱就是说去不了现场&#xff0c;就多听听手机…...

CLion Debug 调试 Makefile 构建的 C 语言程序断点不起作用

最近在研究 jattach&#xff0c;打算在本地调试项目&#xff0c;发现 CLion 可以正常编译运行代码&#xff0c;却无法断点 Debug。由于笔者对 C/C 项目不熟悉&#xff0c;在此记录研究过程中遇到的一些基本问题与解决方法。 文章目录解决方式尝试过的手段【未解决】找 Native D…...

·神经网络

目录11神经网络demo112神经网络demo213神经网络demo320tensorflow2.0 安装教程,所有安装工具&#xff08;神经网络&#xff09;21神经网络-线性回归- demo122神经网络-线性回归- demo228神经网络-多层感知- demo1目录11神经网络demo1 package com.example.xxx; import java.ut…...

【Java 多线程学习】

多线程学习多线程1. 并行与并发2.进程和线程3. *****多线程的实现方式3.1 继承Thread类的方式进行实现3.2 实现Runnable接口方式进行实现3.3 利用Callable和Future接口方式实现3.4 设置获取线程名字4.获得线程对象5.线程休眠6.线程调度[线程的优先级]7.后台线程/守护线程多线程…...

【计算机考研408】快速排序的趟数问题 + PAT 甲级 7-2 The Second Run of Quicksort

前言 该题还未加入PAT甲级题库中&#xff0c;可以通过购买2022年秋季甲级考试进行答题&#xff0c;纯考研题改编 快速排序 常考的知识点 快速排序是基于分治法快速排序是所有内部排序算法中平均性能最优的排序算法快速排序是一种不稳定的排序算法快速排序算法中&#xff0c…...

CSS-Grid(网格)布局

前言 之前HTML 页面的布局基本上都是通过 Flexbox 来实现的&#xff0c;能轻松的解决复杂的 Web 布局。 现在又出现了一个构建 HTML 最佳布局体系的新竞争者。就是强大的CSS Grid 布局。 grid和flex区别是什么&#xff1f;适用什么场景&#xff1f; Flexbox 是一维布局系统&am…...

软件测试4

一 form表单标签 1.form表单标签里面就是所有用户填写的表单数据&#xff1b; action“xxx.py”把表单数据提交给哪一个后台程序去处理 method“post” 传递数据时候的方式方法&#xff0c;post代表隐式提交数据、get明文传送数据 2.input标签的type类型 type“text” 普通的输…...

996的压力下,程序员还有时间做副业吗?

996怎么搞副业&#xff1f; 这个问题其实蛮奇怪的&#xff1a;996的压力下&#xff0c;怎么会还想着搞副业呢&#xff1f; 996还想搞副业的原因有哪些&#xff1f; 大家对于996应该都不陌生&#xff0c;总结就是一个字&#xff1a;忙。 996的工作性质就是加班&#xff0c;就…...

每日学术速递3.1

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Directed Diffusion: Direct Control of Object Placement through Attention Guidance 标题&#xff1a;定向扩散&#xff1a;通过注意力引导直接控制物体放置 作者&#xff1a;…...

金融行业数据模型

一、Teradata FS-LDM Teradata 公司基于金融业务发布的FS-LDM&#xff08;Financial Servies Logical Data Model&#xff09; 十大主题&#xff1a;当事人、产品、协议、事件、资产、财务、机构、地域、营销、渠道。 1、当事人&#xff08;Party&#xff09; 银行所服务的任…...

【面试题】2023前端vue面试题及答案

Vue3.0 为什么要用 proxy&#xff1f;在 Vue2 中&#xff0c; 0bject.defineProperty 会改变原始数据&#xff0c;而 Proxy 是创建对象的虚拟表示&#xff0c;并提供 set 、get 和 deleteProperty 等处理器&#xff0c;这些处理器可在访问或修改原始对象上的属性时进行拦截&…...

(哈希查找)leetcode128. 最长连续序列

文章目录一、题目1、题目描述2、基础框架3、原题链接二、解题报告1、思路分析2、时间复杂度3、代码详解三、本题小知识一、题目 1、题目描述 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。…...

js中splice方法和slice方法

splice方法用来操作数组splice(startIndex,deleteNum,item1,....,)此操作会改变原数组。删除数组中元素参数解释&#xff1a;startIndex为起始index索引。deleteNum为从startIndex索引位置开始需要删除的个数。分三种情况&#xff1a;没有传第三个参数的情况下&#xff0c;dele…...

c++ argparse

需求 c程序传参数&#xff0c;像python中argparse一样方便。 方法1 用gflags 参考https://heroacool.blog.csdn.net/?typeblog git clone https://github.com/gflags/gflags cd gflags # 进入项目文件夹 cmake . # 使用 cmake 编译生成 Makefile 文件 make -j 24 # make 编…...

内大892复试真题16年

内大892复试真题16年 1. 输出三个数中较大数2. 求两个数最大公约数与最小公倍数3. 统计字符串中得字符个数4. 输出菱形5. 迭代法求平方根6. 处理字符串(逆序、进制转换)7. 寻找中位数8. 输入十进制输出n进制1. 输出三个数中较大数 问题 代码 #include <iostream>usin…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...

DiscuzX3.5发帖json api

参考文章&#xff1a;PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下&#xff0c;适配我自己的需求 有一个站点存在多个采集站&#xff0c;我想通过主站拿标题&#xff0c;采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...

webpack面试题

面试题&#xff1a;webpack介绍和简单使用 一、webpack&#xff08;模块化打包工具&#xff09;1. webpack是把项目当作一个整体&#xff0c;通过给定的一个主文件&#xff0c;webpack将从这个主文件开始找到你项目当中的所有依赖文件&#xff0c;使用loaders来处理它们&#x…...