roadhog-umi-dva

1. roadhog

  • roadhog 是基于 webpack 的封装工具,目的是简化 webpack 的配置
  • 提供 server、 build 和 test 三个命令,分别用于本地调试和构建
  • 提供了特别易用的 mock 功能
  • 命令行体验和 create-react-app 一致,配置略有不同,比如默认开启 css modules
  • 还提供了 JSON 格式的配置方式。

根目录新建.roadhogrc.mock.js文件 mock本地数据

1.1 内部是如何实现的

1、本地全局安装npm install roadhog -g
2、npm root -g 查看全局安装的路径,找到roadhog/package.json
可以看到

1
2
3
"bin":{
"roadhog": "./bin/roadhog.js"
}

3、查看这个roadhog.js文件,部分源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env node

const spawn = require('cross-spawn');
const chalk = require('chalk'); // 可以在命令行显示颜色
const script = process.argv[2];
//node bin/roadhog.js -v
switch (script) {
case "-v":
case "--version":
console.log(require("../package.json").version);
break;
case "build":
case "server":
result = spawn.sync(
"node",
[require.resolve(`../lib/${script}`)],
{ stdio: "inherit" }
);
process.exit(result.status);
break;
default:
console.log(`Unknown script ${chalk.cyan(script)}.`);
break;
}

获取到命令行输入的参数 process.argv 根据不同的参数执行相应的文件
例如这里执行的是build,在node环境中,读取的是../lib/build.js文件
lib/build.js 缩减版如下:

1
2
3
4
5
6
7
8
9
10
11
const webpack = require("webpack");
const chalk = require("chalk");
function doneHandler(err, stats) {
console.log(stats.toJson().assets);
}
function build() {
let config = require("lib/config/webpack.config.prod");
var compiler = webpack(config);
compiler.run(doneHandler);
}
build();

会去读取 webpack.config.prod 配置文件,再交给webpack编译打包
4、lib\config\webpack.config.prod.js 简单配置如下:

1
2
3
4
5
6
7
8
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve('dist'),
filename: "[name].js"
}
};

5、配置自定义的打包配置
根目录新建.roadhogrc 或 .roadhogrc.js

2. UmiJs

  • UmiJS 是一个类 Next.JS 的 react 开发框架。
  • 他基于一个约定,即 pages 目录下的文件即路由,而文件则导出 react 组件
  • 然后打通从源码到产物的每个阶段,并配以完善的插件体系,让我们能把 umi 的产物部署到各种场景里。

约定 src/layouts/index.js 为全局路由,返回一个 React 组件,所以编译顺序是先去编译/layouts/下的index.js,读取到React组件,然后去编译pages下的页面内容。

核心工作:
1、动态检测项目,生成配置文件(pages/.umi/history.js|router.js)

会监控pages文件夹,有新的文件生成就改变.umi/文件夹下的配置

1
2
3
4
let fs = require('fs');
fs.watchFile('**',function(){
...监控文件变化,执行重新生成配置文件的操作
})

3. dva

  • 基于 redux、redux-saga 和 react-router 的轻量级前端框架。
  • dva是基于react+redux最佳实践上实现的封装方案,简化了redux和redux-saga使用上的诸多繁琐操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import React from 'react';
import dva,{connect} from 'dva';
import keymaster from 'keymaster';
import { Router, Route } from 'dva/router';
//dva react react-dom redux redux-saga react-router react-router-dom history
const app = dva();
//redux combineReducers reducer都有自己的状态
//combineReducers({counter:counterReducer})
//总的状态树 state={counter:0,counter2:0}
const delay = (millseconds)=>{
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve();
},millseconds);
});
}
app.model({
namespace:'counter',
state:{number:0},
reducers:{//接收老状态,返回新状态
add(state){ //dispatch({type:'add'});
return {number:state.number+1};
},
minus(state){//dispatch({type:'minus'})
return {number:state.number-1};
}
},
// 延时操作 调用接口 等待
effects:{
*asyncAdd(action,{put,call}){ //redux-saga/effects {put,call}
yield call(delay,1000);//把100传给delay并调用,yield会等待promise完成
yield put({type:'add'});
}
},
subscriptions:{
keyboard({dispatch}){ // 监听键盘输入,是空格键 就执行add方法
keymaster('space',()=>{
dispatch({type:'add'});
});
},
changeTitle({history}){ // 当路径发生变化的时候执行回调函数
setTimeout(function(){
history.listen(({pathname})=>{
document.title = pathname;
});
},1000);

}
}
});
app.model({
namespace:'counter2',
state:{number:0},
reducers:{//接收老状态,返回新状态
add(state){ //dispatch({type:'add'});
return {number:state.number+1};
},
minus(state){//dispatch({type:'minus'})
return {number:state.number-1};
}
}
});
const Counter = (props)=>{
return (
<div>
<p>{props.number}</p>
<button onClick={()=>props.dispatch({type:'counter/add'})}>add</button>
<button onClick={()=>props.dispatch({type:'counter/asyncAdd'})}>asyncAdd</button>
<button onClick={()=>props.dispatch({type:'counter/minus'})}>-</button>
</div>
)
}
const Counter2 = (props)=>{
return (
<div>
<p>{props.number}</p>
<button onClick={()=>props.dispatch({type:'counter2/add'})}>+</button>
<button onClick={()=>props.dispatch({type:'counter2/minus'})}>-</button>
</div>
)
}
//{counter1:{number:0},counter2:{number:0}}
const ConnectedCounter = connect(
state=>state.counter
)(Counter);
const ConnectedCounter2 = connect(
state=>state.counter2
)(Counter2);
app.router(
({app,history})=>(
<Router history={history}>
<>
<Route path="/counter1" component={ConnectedCounter}/>
<Route path="/counter2" component={ConnectedCounter2}/>
</>
</Router>
)
);
app.start('#root');

[官网]https://dvajs.com/guide/

注意: 这里最后的手写dva可以看一下,都是基于redux react-redux react-router-dom redux-saga connected-react-router history的封装:http://www.zhufengpeixun.cn/2020/html/31.dva.html#t43.1%20src\index.js

1
import { routerMiddleware, connectRouter, ConnectedRouter } from "connected-react-router";

小结

  • roadhog 是基于 webpack 的封装工具,目的是简化 webpack 的配置
  • umi 可以简单地理解为 roadhog + 路由,思路类似 next.js/nuxt.js,辅以一套插件机制,目的是通过框架的方式简化 React 开发
  • dva 目前是纯粹的数据流,和 umi 以及 roadhog 之间并没有相互的依赖关系,可以分开使用也可以一起使用,个人觉得 umi + dva 是比较搭的

参考文章

http://www.zhufengpeixun.cn/2020/html/30.cms-8-roadhog.html
http://www.zhufengpeixun.cn/2020/html/30.cms-10-umi.html
https://v2.umijs.org/zh/guide/
http://www.zhufengpeixun.cn/2020/html/30.cms-12-dva.html