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' } });
<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的组成
- 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" 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
使用方式:
- 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" 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);




版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/15166.html