一、知识点的补充:
1、Webpack 是一个前端资源加载/打包工具,只需要相对简单的配置就可以提供前端工程化需要的各种功能。
2、webpack的模块热替换(HMR):如果js引入的css等发生改变的时候,不用刷新, 一些变量发生变化,但是这些变量还没有被使用的时候,是允许不刷新的,但是当修改积累到一定程度,webpack-dev-server会刷新页面的。
3、dva-cli创建新项目:
步骤:接到需求之后推荐的做法不是立刻编码,而是先以上帝模式做整体设计。
step1:先设计 model
step2:再设计 component
step3:最后连接 model 和 component
4、基于dva-cli&antd的react项目实战:http://que01.github.io/2016/11/20/dva-react/
5、keymaster库的下载地址:https://github.com/madrobby/keymaster
6、Keymaster.js快速绑定键盘操作的JavaScript库:http://www.uedsc.com/keymaster-js.html
Keymaster.js是一个JavaScript的库,用户绑定键盘的快捷方式操作,他是一个轻量级的插件,压缩版本不到100K,不依赖任何第三方插件库,能支持多种键盘按钮和组合按键。
二、项目场景描述
转自:https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/getting-started.md
最终效果:
这是一个测试鼠标点击速度的 App,记录 1 秒内用户能最多点几次。顶部的 Highest Record 纪录最高速度;中间的是当前速度,给予即时反馈,让用户更有参与感;下方是供点击的按钮。
看到这个需求,我们可能会想:
- 该如何创建应用?
- 创建完后,该如何一步步组织代码?
- 开发完后,该如何构建、部署和发布?
在代码组织部分,可能会想:
- 如何写 Component ?
- 如何写样式?
- 如何写 Model ?
- 如何 connect Model 和 Component ?
- 用户操作后,如何更新数据到 State ?
- 如何处理异步逻辑? (点击之后 +1,然后延迟一秒 -1)
- 如何处理路由?
以及:
- 不想每次刷新 Highest Record 清 0,想通过 localStorage 记录,这样刷新之后还能保留 Highest Record。该如何处理?
- 希望同时支持键盘的点击测速,又该如何处理?
我们可以带着这些问题来看这篇文章,但不必担心有多复杂,因为全部 JavaScript 代码只有 70 多行。
具体描述和讲解可以看链接。
安装 dva-cli
你应该会更希望关注逻辑本身,而不是手动敲入一行行代码来构建初始的项目结构,以及配置开发环境。
那么,首先需要安装的是 dva-cli 。dva-cli 是 dva 的命令行工具,包含 init、new、generate 等功能,目前最重要的功能是可以快速生成项目以及你所需要的代码片段。
$ npm install -g dva-cli
安装完成后,可以通过 dva -v
查看版本,以及 dva -h
查看帮助信息。
创建新应用
安装完 dva-cli 后,我们用他来创建一个新应用,取名 myApp
。
$ dva new myApp --demo
注意:--demo
用于创建简单的 demo 级项目,正常项目初始化不加要这个参数。
然后进入项目目录,并启动。
$ cd myApp
$ npm start
几秒之后,会看到这样的输出:
proxy: listened on 8989
livereload: listening on 35729
===================================================================================================下面是我对代码的进一步布局和梳理==================
接到需求之后推荐的做法不是立刻编码,而是先以上帝模式做整体设计。
先设计 model
再设计 component
最后连接 model 和 component
项目目录结构展示:
圈出来的是自己新建的。。。。
第一步:设计model
在models中countAppModel.js文件如下:
import key from 'keymaster';//?????为什么dva-cli 没有自动下载该依赖
/**2、
Store 收到 Action 以后,必须给出一个新的 State对象(注意:不是改变值,是生产新的对象。),这样 View 才会发生变化。
这种 State 的计算过程就叫做 Reducer。
在Redux中,reducer就是获得这个应用的当前状态和事件然后返回一个新状态的函数。
通过获取当前state和一个action作为参数,再返回下一个state:
store.dispatch方法会触发 Reducer 的自动执行。
Reducer 函数最重要的特征是,它是一个纯函数。
也就是说,只要是同样的输入,必定得到同样的输出。
Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,
对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。
*add() {} 等同于 add: function*(){}
call 和 put 都是 redux-saga 的 effects,call 表示调用异步函数,put 表示 dispatch action,其他的还有 select, take, fork, cancel 等,详见 redux-saga 文档
默认的 effect 触发规则是每次都触发(takeEvery),还可以选择 takeLatest,或者完全自定义 take 规则
*/
/**
2. Model
count 表示 model state 在全局 state 所用的 key,state 是默认数据
record 表示 highest record
current 表示 当前速度
add(state) 表示 add: function(state) {}
{ ...state } 表示 "..." 是对象扩展运算符,类似 Object.extend
*/
function delay(timeout){
return new Promise(resolve => {
setTimeout(resolve, timeout);
});
}
/**
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,
当此类行为会改变数据的时候可以通过 dispatch 发起一个 action
*/
export default {
namespace:'count',
state:{
record:0,
current:0,
},
/**
同步行为会直接通过 Reducers 改变 State.
Reducer 则是描述如何改变数据的
Reducer 必须是纯函数,所以同样的输入必然得到同样的输出,
*/
reducers: {
add(state) {
const newCurrent = state.current + 1;
return { ...state,
record: newCurrent > state.record ? newCurrent : state.record,
current: newCurrent,
};
},
minus(state) {
return { ...state, current: state.current - 1};
},
},
/**
异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State.
它使得我们的函数变得不纯,同样的输入不一定获得同样的输出。
dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,
由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。
*/
effects: {
*add(action, { call, put })
{
yield call(delay, 1000);
yield put({ type: 'minus' });
},
},
/**
Subscriptions 是一种从 源 获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。
数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
*/
subscriptions: {
keyboardWatcher({ dispatch }) {
/*multiple shortcuts that do the same thing*/
key('⌘+up, ctrl+up',()=>{dispatch({type:'add'})});
},
},
};
第二步,设计component
在components文件夹中CountAppComponent.js:
import styles from '../index.less';//styles是个变量;代表index.less文件对象
/**
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。
无论是从 UI 事件、网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。
action 必须带有 type 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch 函数;需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的。
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,
但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式.而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,
dispatch({type:'count/add'});如果在 model 外调用,需要添加 namespace,即:count
*/
const CountApp = ({count,dispatch})=>{
return (
<div className={styles.normal}>
<div className={styles.record}>Highest Record:{count.record}</div>
<div className={styles.current}>{count.current}</div>
<div className={styles.button}>
<button onClick={
()=>{
dispatch({type:'count/add'});
}
}>+</button>
</div>
</div>
);
};
export default CountApp;
第三步:连接component和model
import CountApp from '../components/CountAppComponent';
import { connect } from 'dva';
/**
在组件设计方法中,我们提到过 Container Components,
在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。
所以在 dva 中,通常需要 connect Model的组件都是 Route Components,
组织在/routes/目录下,而/components/目录下则是纯组件(Presentational Components)
*/
function mapStateToProps(state){
return {count:state.count};
}
export default connect(mapStateToProps)(CountApp);
第四步:设计浏览器url路径
在src文件夹router.js文件如下:
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import IndexPage from './routes/IndexPage';
import mapStateToProps from './routes/MapStateToProps';
/**
3. Router
history 默认是 hashHistory 并且带有 _k 参数,
可以换成 browserHistory,也可以通过配置去掉 _k 参数。
这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,
通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。
*/
function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={IndexPage} />
<Route path="/mapStateToProps" exact component={mapStateToProps} />
</Switch>
</Router>
);
}
export default RouterConfig;
第五步:修改项目入口文件index.js
在src目录下index.js文件如下:
import dva from 'dva';
// 1. Initialize
const app = dva();
console.log(app._store);//可以看到顶部的state数据;
//2. Model
app.model(require('./models/countAppModel').default);
/**
3.Router
dva 实例提供了 router 方法来控制路由,使用的是react-router
*/
app.router(require('./router').default);
// 4. Start
app.start('#root');
第六步:浏览器页面渲染载体页面index.html
在根项目文件夹public的index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dva Demo</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div id="root"></div>
<script src="index.js"></script>
<script src="keymaster.js"></script>
</body>
</html>
其他:routes文件夹中的IndexPage.js页面:
import React from 'react';
import { connect } from 'dva';
function IndexPage() {
return (
<div>Hello Dva.</div>
);
}
IndexPage.propTypes = {
};
export default connect()(IndexPage);
src目录下的index.less:
.normal{
width:200px;
margin:100px auto;
padding:20px;
border:1px solid #ccc;
box-shadow:0 0 20px #ccc;
}
.record {
border-bottom: 1px solid #ccc;
padding-bottom: 8px;
color: #ccc;
}
.current {
text-align: center;
font-size: 40px;
padding: 40px 0;
}
.button {
text-align: center;
button {
width: 100px;
height: 40px;
background: #aaa;
color: #fff;
}
}
运行项目:
Hightest Record:22是按Ctrl+↑快捷键实现的1秒内按键最对的次数。
如果步骤顺序不对,欢迎指点☺
改正上面的顺序:
参考GitHub指南:
step1:先准备好dva基本框架;
step2:再为项目配置路由;
step3:然后设计页面维度的容器routes文件夹的内容;
step4:设计component组件的内容;
step5:给 Model 添加 Reducers、Effects;
step6:将请求相关(与后台系统的交互)从models中抽离出来,单独放到 /services/中
step7:在 dva 中,配套的工具能够很方便的模拟数据,可以脱离服务器复杂的环境进行模拟的本地调试开发,mock 数据。(用roadhog,支持类似的mock方案)(dora-plugin-proxy本库已久未维护。)
step8:在 dva 中,所有的页面都是基于组件的。希望样式依附于组件,不同组件的样式相互之间不会造成污染。添加样式
step9:设计布局。
最后欢迎看官们浏览,记得点赞或评赞哦~