前言
提示:本文章来自珠峰培训周啸天老师的视频讲解总结
视频地址:
114.关于dva-cli和roadhog的使用_哔哩哔哩_bilibili
dva:
https://github.com/dvajs/dva/tree/master/docs/api 「官网好像打不开」
antd: 全局化配置 ConfigProvider - Ant Design
redux-saga: 高级 · Redux-Saga
一、dva脚手架
1、创建dva项目
全局安装dva命令:npm install dva-cli -g
查看已安装dva命令:dva -v
创建dva项目命令:dva new project-name
2、修改运行的host,端口号
创建.env文件,代码如下
HOST=127.0.0.1
PORT=3004
或者在package.json中修改,要先安装cross-env,然后在scripts中进行配置例如:
"start": "cross-env HOST=127.0.0.1 PORT=3000 roadhog server",
//安装包版本
"cross-env": "7.0.3",
3、dva配置
默认有.webpackrc「默认JSON格式,也可以改成.webpackrc.js」文件,我们在该文件下配置
代码如下
{
"hash": true,
"html": {
"template": "./public/index.ejs"
},
"disableCSSModules": true,
"disableCSSSourceMap": true,
"extraBabelPlugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}]
],
"proxy": {
"/operating/api": {
"target": "https://",
"changeOrigin": true,
"pathRewrite": { "^": "" }
}
},
"env": {
"development": {
"extraBabelPlugins": ["dva-hmr"],
"browserslist": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"production": {
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
}
}
}
修改网页图标在public目录下添加logo.png在html文件中加入如下代码
<link rel="shortcut icon" href="./logo.png" type="image/x-icon"/>
默认加了一个注释文件防止后期忘记
### 安装包
{
"prop-types": "^15.8.1",
"qs": "6.6.0",
<!-- 在jsx中直接写less代码 -->
"styled-components": "5.3.6",
"antd": "4.24.7",
"antd-icons": "0.1.0-alpha.1",
<!-- antd按需加载 -->
"babel-plugin-import": "1.13.5",
<!-- less预编译 -->
"less": "4.1.3",
"less-loader": "8.1.1"
<!--REM响应式布局处理,移动端需要 -->
"lib-flexible": "0.3.2",
"postcss-pxtorem": "5.1.1",
"babel-plugin-styled-components-px2rem": "1.5.5",
<!-- -->
"history": "4.10.1",
"cross-env": "7.0.3",
<!-- es6处理 -->
"@babel/polyfill": "7.12.1",
<!-- 修改主机名与端口号 -->
"start": "cross-env HOST=127.0.0.1 PORT=3000 roadhog server",
}
<!--.webpackrc文件配置 -->
{
<!-- 对css的处理 -->
"disableCSSModules": true,
"disableCSSSourceMap": true,
<!-- 对postcss-loader的插件扩展 -->
"extraPostCSSPlugins": [
<!-- 插件配置有问题 -->
"postcss-pxtorem", { "rootValue": 75, "propList": ["*"] }
],
<!-- 对babel扩展应用的插件 -->
"extraBabelPlugins": [
["import", {
<!-- antd按需加载 -->
"libraryName": "antd",
"libraryDirectory": "es",
"style": true
}],
<!-- styled-components中的px2rem -->
["styled-components-px2rem", { "rootValue": 75 }]
],
<!-- 开发环境的跨域代理 -->
"proxy": {
"/operating/api": {
"target": "https://dev-operating.hewomall.com",
"changeOrigin": true,
"pathRewrite": { "^": "" }
}
},
<!-- 配置浏览器兼容列表「区分环境」 -->
"env": {
"development": {
<!-- 开启热更新 -->
"extraBabelPlugins": ["dva-hmr"],
"browserslist": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"production": {
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
}
}
}
4、目录结构
5、程序入口
src/index.js文件
import dva from 'dva';
/**
默认情况下roadhog脚手架已经完成了对ES6常规语法「不含装饰器等非规范语法,这些语法
还需要我们单独安装babel插件处理」,以及css3的语法兼容处理
+ es6语法:babel-loader/babel-preset-react-app
+ css语法:postcss-loader/autoprefixer
--->设置我们的浏览器兼容列表
*/
import '@babel/polyfill';
import { createHashHistory } from 'history';
import voteModel from './models/vote'; //此model打包到主js中
import demoModel from './models/demo';
import createLoading from 'dva-loading';
// import { createLogger } from 'redux-logger';
// 1. Initialize
const app = dva({
// 指定路由的模式,默认是hash路由: createHashHistory/createBrowserHistory
history: createHashHistory(),
// 扩展其他的中间件,例如:redux-logger/redux-persist...
extraEnhancers: [],
// 给Model板块赋初始值,它的优先级要高于每个板块中设置的初始状态值
// 一定要保证操作的这个板块不是懒加载的
initialState: {
// vote: {
// oppNum: 100,
// supNum: 50
// },
// demo: {
// num: 100,
// }
},
// 每一次派发进行拦截
// onAction: [createLogger()]
});
// 2. Plugins
//一般使用了dva-loading,它默认在redux公共状态中就会加一个loading状态
app.use(createLoading()); //它的值是一个对象{global: false,models: {...}, effects: {...}}
// 3. Model
app.model(voteModel); // 一加载页面,我们就基于app.model注册相关的model使用
app.model(demoModel);
// 4. Router
app.router(require('./router').default);
// 5. Start 在这之前一定要先处理router
app.start('#root');
二、路由
1、路由文件routes「创建项目默认会有」
dva/router
+ react-router-dom 中提供的所有api「v5-」
+ react-router-redux 「routerRedux」中的方法:把路由和redux结合在一起的一个模块,
可以允许我们在redux操作中,去实现路由的跳转以及传参...
路由模式
hash路由模式:地址上带#
history路由模式:浏览器路由模式,不带#
2、路由的配置
import { Router, Route, Switch, Redirect } from "dva/router";
基于<HashRouter>把所有要渲染的内容包起来,开启HASH路由
后续用到的<Link>、<Route>等,都需要在HashRouter中使用
开启后,整个页面地址默认会设置一个 #/ 哈希值
NavLink/Link实现路由切换/跳转的组件
它可以根据路由模式,自动设置点击A的切换方式
最后渲染完毕后的结果依然是A标签
Switch:
确保路由中,只要有一项匹配,则不再继续向下匹配
exact:
设置匹配模式为精准匹配
Redirect:
from:从哪个地址来
to:重定向的地址
exact是对from地址的修饰,开启精准匹配
路径地址匹配的规则:
页面地址:浏览器URL后面的哈希值
路由地址:Route中path字段指定的地址
代码如下
function RouterConfig({ history, app }) {
/**
history: 包含路由跳转方法的history对象,
app: 基于dva创建的应用
*/
return (
<ConfigProvider locale={zhCN}>
<Router history={history}>
<Switch>
<Route path="/" exact component={Vote} />
<Route path="/demo" component={LazyDemo} />
<Route path="/personal" component={LazyPersonal} />
<Redirect path="*" to="/" />
</Switch>
</Router>
</ConfigProvider>
);
}
3、路由跳转
NavLink、Link跳转
<NavLink to="/personal/order">我的订单</NavLink>
<Link to={{
pathname: '/personal/profile',
search: '?id=100&name=zhufeng'
}}>按钮</Link>
编程试导航路由跳转
1、history
1)、正常跳转
history.push('/personal/profile');
2)、问号传参
history.push({
pathname: '/personal/profile',
search: '?id=100&name=zhufeng'
});
3)、路径传参
// 路径传参:把传递的信息当做路由地址的一部分,但是需要路由地址基于":?"设置匹配规则
history.push('/personal/profile/1001/zs');
4)、隐式传参
// 隐式传参:基于state把信息传递给目标组件,但是传递的信息没有在地址栏中,刷新页面
// 传递的信息就会消失了
history.push({
pathname: '/personal/profile',
state: { id: `1001x`, name: 'zs'}
});
2、routerRedux
/**
routerRedux是react-router-redux中提供的对象,此对象中包含了路由跳转的方法
+ go/goBack/goForward
+ push/replace
相比较于props.history对象来讲,routerRedux不仅可以在组件中实现路由跳转,还可以在
redux操作中实现路由跳转,它本身就是redux与router的结合操作
+ 组件中「redux外部」 dispatch(routerRedux.push(...))
+ 在redux内部 yield put(routerRedux.push(...))
+ 注意:一定要基于dispatch进行派发才会跳转,因为执行routerRedux.xxx(...)返回的是
一个action对象,
{
payload: {method: 'push', args: Array(1)},
type: "@@router/CALL_HISTORY_METHOD"
}
*/
1)、正常跳转
dispatch(routerRedux.push('/personal/profile'));
2)、问号传参
dispatch(routerRedux.push({
pathname: '/personal/profile',
search: '?id=100&name=zhufeng'
}));
3)、路径传参
// 路径传参:把传递的信息当做路由地址的一部分,但是需要路由地址基于":?"设置匹配规则
dispatch(routerRedux.push('/personal/profile/1001/xiaohu'));
4)、隐式传参
// 隐式传参:基于state把信息传递给目标组件,但是传递的信息没有在地址栏中,刷新页面
// 传递的信息就会消失了
dispatch(routerRedux.push({
pathname: '/personal/profile',
state: {
id: `1001x`, name: 'zs'
}
}));
4、接收路由跳转传的参数
根据组件中的props对象中的location、match、history这三个属性接收、假设这个组件并不是基于路由匹配渲染的,那么默认情况下,他的属性中是没有history、match、location这三个对象,此时我们可以基于withRouter高阶组件,对其处理,让其具备这三个对象属性
/**
location对象:
+ pathname: 当前的路由地址
+ search: 获取问号传参的信息 -> "?xxx=xxx"
+ state: 获取隐式传参的信息
match对象
+ params: 存储基于路径参数传递过来的信息「对象」
*/
import { withRouter } from 'dva/router';
export default withRouter(MyProfile);
5、路由懒加载
import dynamic from 'dva/dynamic';
const LazyDemo = dynamic({
app: window.app,
models: () => [import(/* webpackChunkName: 'demo' */'./models/demo')],
component: () => import(/* webpackChunkName: 'demo' */"./routes/Demo")
});
// /**路由懒加载 */
// const LazyOrder = dynamic({
// // app: window.app,
// models: () => [],
// component: () => import(/*webpackChunkName: 'MyOrder'*/'./personal/MyOrder'),
// });
三、Modal数据模型
models下对redux-saga的封装,Modal数据模型,包含了每个状态的管理,models模板代码如下
/**dva第三部分核心:Modal数据模型,包含了每个状态的管理 */
export default {
/**模块名 */
namespace: 'personal',
/**公共状态 */
state: {
info: null,
},
/**基于saga的语法,异步修改公共状态 */
effects: {
},
/**同步修改公共状态 */
reducers: {
},
};
使用models要在index.js文件中注册
models具体使用
const delay = function delay(interval = 1000){
return new Promise(resolve => {
setTimeout(() => {
resolve(`${interval}@@saga`);
}, interval);
});
}
/**dva第三部分核心:Modal数据模型,包含了每个状态的管理 */
export default {
/**模块名 */
namespace: 'demo',
/**公共状态 */
state: {
num: 10,
},
/**同步修改公共状态 */
reducers: {
increment(state, { payload = 1}) {
return {
...state,
num: state.num + payload
}
}
},
/**基于saga的语法,异步修改公共状态 */
effects: {
/**
redux-saga中,我们基于take/takeLatest/takeEvery等方式创建监听器,此时写成一个个的
Generator函数即可--> 默认是基于takeEvery的方式创建的监听器
+ 方法名就是我们创建监听器的名字
+ 方法就是派发的任务被监听后,执行的working方法
+ 此处的函数名不要和reducers中的函数名一致,因为每一次派发reducers和effects中的方法
都去匹配执行!!如果函数名一致,则状态修改两次,所以我们一般在effects中写的名字都加Async
方法中的参数
+ action:在组件中进行任务派发时,传递的action对象
+ 第二个参数就是redux-saga中提供的EffectsAPI,但是没有delay/debounce...
+ 基于yield select() 可以获取所有模块的公共状态
*/
// *incrementAsync(action, saga) {
// // console.log(action);//{type: 'demo/incrementAsync', payload: 10, __dva_resolve: ƒ, __dva_reject: ƒ}
// // console.log(saga);//{__esModule: true, take: ƒ, takem: ƒ, put: ƒ, all: ƒ, …}
// // let state = yield select(); //获取所有模块的公共状态
// // console.log(state); // {routing: {…}, @@dva: 0, vote: {…}, demo: {…}}
// },
// 如果想设置不同的监听器,可以这样写
incrementAsync:[
// 数组第一项是working函数
function* ({ payload }, { call, put }) {
yield call(delay, 2000);
yield put({
type: 'increment',
payload
})
},
// 数组第二项是指定监听器的类型
{ type: 'takeLatest'}
]
},
/**
在这个订阅的方法,会在页面一加载的时候就会被通知执行,所以:
+ 我们可以把页面一加载需要处理的事情「和redux相关的」在这里处理
+ 在这里也可以基于history.listen做事件监听,保证进入哪个组件再处理也可以
demoModel是被懒加载的,只有访问了/demo这个地址(组件),demoModel才会被注册
+ 只有进入到组件,Model懒加载完毕,也被注册了,subscriptions中订阅的方法才会被执行
+ 而且只会执行一次,后期路由来回切换的时候,也不再执行了
*/
subscriptions: {
setup() {
console.log('DEMO-SETUP');
}
},
};
// const saga = function* saga() {
// yield takeEvery('incrementAsync', working);
// }
组件中使用
import React, { useState } from "react";
import styled from "styled-components";
import { Button } from 'antd';
import { connect } from 'dva';
const DemoBox = styled.div`
margin: 32px auto;
padding: 16px;
border: 1px solid #ccc;
width: 300px;
span {
display: block;
margin-bottom: 16px;
}
`;
const Demo = function Demo(props) {
let { num = 0, dispatch, loading } = props;
// 让loading的状态值和指定的模块的effects方法进行关联,每一次派发这个任务之前
// loading = true;派发任务结束后,loading = false
loading = loading.effects['demo/incrementAsync'];
// let [loading, setLoading] = useState(false);
return <DemoBox>
<span>{num}</span>
<Button type='primary' style={{marginRight: 16}} onClick={() => {
// 这样派发是通知reducers/effects中的方法执行,
// reducers同步修改状态,effects是异步修改状态
dispatch({
type: 'demo/increment',
payload: 20
});
}}>累加器同步</Button>
<Button type='primary' danger onClick={async () => {
/**
前提:异步派发返回的结果是Promise实例,派发成功后才改变实例状态为成功
派发失败后,实例状态是失败
+ dva中处理这样的事情,可以基于此方案处理按钮的Loading状态,实现防抖
+ 但是原生的redux-saga中,每一次异步派发并不返回Promise实例,这样的
操作就失效了
*/
// setLoading(true);
await dispatch({
type: 'demo/incrementAsync',
payload: 10
});
// setLoading(false);
}} loading={loading}>累加器异步</Button>
</DemoBox>
}
export default connect(state => {
return {
...state.demo,
loading: state.loading
}
})(Demo);
redux-saga的使用可以参考React系列之Redux-saga文章「等有时间写」
总结
dva的基本使用,包含了dva脚手架、dva/router路由、models数据模型