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的组成
- namespace 命名空间【模块名,后期获取状态和派发的标识】
state 数据 【模块管理的公共状态】
reducers 同步处理的方法 【已一个一个方法的模式,完成reducer中的派发行为标识的判断以及状态的更改+同步修改+外部修改+外部派发dispatch('/demo/xxx')】
effects redux-saga中异步处理方法【实现异步操作,异步派发】
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使用方式:
- npm or yarn 安装dva-loading ,并在入口Index.js中引入, 示例:import createLoading from 'dva-loading';
- 在入口Index.js中 app.use( createLoading ) ,示例:app.use(createLoading());
- 在组件context高阶函数中 state可以拿到loading ,示例:state => {
return {
...state.demo,
loading: state.loading
};
}- 组件内指定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 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架! 介绍 | DvaJS 易学易用,仅有 6 个 api&…...
数据结构:堆的实现与建堆时间复杂度分析
目录 前言 一.堆的介绍 1.堆的本质 2.堆的分类 二.堆的实现(以小根堆为例) 1.关于二叉树的两组重要结论: 2.堆的物理存储结构框架(动态数组的简单构建) 3. 堆元素插入接口(以小根堆为例) 堆尾元素向上调整的算法接口: 4.堆元素插入接口测试 5.堆元素插入…...
对“车辆销售配置器”的认识与理解
概述 中国汽车市场转为存量阶段后,各车企开始从”以产品为中心“转型到”以客户为中心“,产品的个性化配置需求日益丰富。随着竞争的加剧,车企们不仅要提供出色的产品,而且需要提供更加个性化的产品配置和服务,例如&am…...
Linux编译器——gcc/g++(预处理、编译、汇编、链接)
目录 0.程序实现的两大环境 1.gcc如何完成 预处理 编译 汇编 链接 2.动态库与静态库 对比二者生成的文件大小 3. gcc常用选项 0.程序实现的两大环境 任何一个C程序的实现都要经过翻译环境与执行环境。 在翻译环境中又分为4个部分,预编译、编译、汇编与链…...
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来实现一个音乐播放器
前言 最近可以说大麦网很知名了,哈哈还有好多想要用Python来搞抢票脚本的 怎么说呢也不是不行,但是咱今天可不是来搞这个的,我可不抢票,抢了都去不了,上班搞钱啊铁铁们 咱就是说去不了现场,就多听听手机…...
CLion Debug 调试 Makefile 构建的 C 语言程序断点不起作用
最近在研究 jattach,打算在本地调试项目,发现 CLion 可以正常编译运行代码,却无法断点 Debug。由于笔者对 C/C 项目不熟悉,在此记录研究过程中遇到的一些基本问题与解决方法。 文章目录解决方式尝试过的手段【未解决】找 Native D…...
·神经网络
目录11神经网络demo112神经网络demo213神经网络demo320tensorflow2.0 安装教程,所有安装工具(神经网络)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甲级题库中,可以通过购买2022年秋季甲级考试进行答题,纯考研题改编 快速排序 常考的知识点 快速排序是基于分治法快速排序是所有内部排序算法中平均性能最优的排序算法快速排序是一种不稳定的排序算法快速排序算法中,…...
CSS-Grid(网格)布局
前言 之前HTML 页面的布局基本上都是通过 Flexbox 来实现的,能轻松的解决复杂的 Web 布局。 现在又出现了一个构建 HTML 最佳布局体系的新竞争者。就是强大的CSS Grid 布局。 grid和flex区别是什么?适用什么场景? Flexbox 是一维布局系统&am…...
软件测试4
一 form表单标签 1.form表单标签里面就是所有用户填写的表单数据; action“xxx.py”把表单数据提交给哪一个后台程序去处理 method“post” 传递数据时候的方式方法,post代表隐式提交数据、get明文传送数据 2.input标签的type类型 type“text” 普通的输…...
996的压力下,程序员还有时间做副业吗?
996怎么搞副业? 这个问题其实蛮奇怪的:996的压力下,怎么会还想着搞副业呢? 996还想搞副业的原因有哪些? 大家对于996应该都不陌生,总结就是一个字:忙。 996的工作性质就是加班,就…...
每日学术速递3.1
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Directed Diffusion: Direct Control of Object Placement through Attention Guidance 标题:定向扩散:通过注意力引导直接控制物体放置 作者:…...
金融行业数据模型
一、Teradata FS-LDM Teradata 公司基于金融业务发布的FS-LDM(Financial Servies Logical Data Model) 十大主题:当事人、产品、协议、事件、资产、财务、机构、地域、营销、渠道。 1、当事人(Party) 银行所服务的任…...
【面试题】2023前端vue面试题及答案
Vue3.0 为什么要用 proxy?在 Vue2 中, 0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截&…...
(哈希查找)leetcode128. 最长连续序列
文章目录一、题目1、题目描述2、基础框架3、原题链接二、解题报告1、思路分析2、时间复杂度3、代码详解三、本题小知识一、题目 1、题目描述 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。…...
js中splice方法和slice方法
splice方法用来操作数组splice(startIndex,deleteNum,item1,....,)此操作会改变原数组。删除数组中元素参数解释:startIndex为起始index索引。deleteNum为从startIndex索引位置开始需要删除的个数。分三种情况:没有传第三个参数的情况下,dele…...
c++ argparse
需求 c程序传参数,像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…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...

