前端学习系列:基于React的Robot框架的踩坑之旅
背景
上次给大家介绍Robot平台框架,其特点:
- 由nodejs+express+react+bootstrap实现
- UI使用开源UI库:charisma
- React通过browserify+babel打包处理
效果如下:
其中遇到的一些问题:
前端界面框架没有真正的React化,只使用很少一部分,html页面里,还有大量的js引用配置,css引用配置
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// html引用css部分
<link href="../../other_js_lib/charisma/css/charisma-app.css" rel="stylesheet">
<link href='../../other_js_lib/charisma/bower_components/fullcalendar/dist/fullcalendar.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/bower_components/fullcalendar/dist/fullcalendar.print.css' rel='stylesheet' media='print'>
<link href='../../other_js_lib/charisma/bower_components/chosen/chosen.min.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/bower_components/colorbox/example3/colorbox.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/bower_components/responsive-tables/responsive-tables.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/bower_components/bootstrap-tour/build/css/bootstrap-tour.min.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/jquery.noty.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/noty_theme_default.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/elfinder.min.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/elfinder.theme.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/jquery.iphone.toggle.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/uploadify.css' rel='stylesheet'>
<link href='../../other_js_lib/charisma/css/animate.min.css' rel='stylesheet'>
// html中大量引用js的部分
<!-- select or dropdown enhancer -->
<script src="../../other_js_lib/charisma/bower_components/chosen/chosen.jquery.min.js"></script>
<!-- plugin for gallery image view -->
<script src="../../other_js_lib/charisma/bower_components/colorbox/jquery.colorbox-min.js"></script>
<!-- notification plugin -->
<script src="../../other_js_lib/charisma/js/jquery.noty.js"></script>
<!-- library for making tables responsive -->
<script src="../../other_js_lib/charisma/bower_components/responsive-tables/responsive-tables.js"></script>
<!-- tour plugin -->
<script src="../../other_js_lib/charisma/bower_components/bootstrap-tour/build/js/bootstrap-tour.min.js"></script>
<!-- star rating plugin -->
<script src="../../other_js_lib/charisma/js/jquery.raty.min.js"></script>
<!-- for iOS style toggle switch -->
<script src="../../other_js_lib/charisma/js/jquery.iphone.toggle.js"></script>
<!-- autogrowing textarea plugin -->
<script src="../../other_js_lib/charisma/js/jquery.autogrow-textarea.js"></script>
<!-- multiple file upload plugin -->
<script src="../../other_js_lib/charisma/js/jquery.uploadify-3.1.min.js"></script>
<!-- history.js for cross-browser state change on ajax -->
<script src="../../other_js_lib/charisma/js/jquery.history.js"></script>
<!-- application script for Charisma demo -->
<script src="../../other_js_lib/charisma/js/charisma.js"></script>html过多,每个一个界面就会有一个html页面
React界面大的方向使用的是Component开发模式,但每个组件内,还是大最使用最原生的方式开发,下面是其中一个组件的render()方法内部代码:
界面很不好,由于css与js逻辑代码分离,在没有缓存时,经常出现先看到没有样式的界面,再看到整体界面,整体视觉效果很不好
使用的是browserify的express的中间件:browserify-middleware,虽然能解决开发期间每次执行手动执行转换的功能,但问题是修改界面后,每次都手动需新才行。
没有适配移动端,在手机版本上的体验很差
还有很多其他的,都是由上面的问题衍生出来的
调研
为了解决上面的问题,花了几天时间进行调研,主要的调研点:
- browserify是否可以对css进行模块化支持?
结论:webpack更加合适 - webpack的使用,有没有类似browserify-middleware功能?
结论:webpack-dev-server - 双服务器配置:nodejs+express与webpack-dev-server的理解与如何工作?
结论:思维需要变化,下面会具体介绍 - React的UI库:Material-UI的使用?
结论:官网demo例子只有基本组件的使用,学了后,还是无法创造出想要的效果
Robot最新框架
技术集
- 后端:
- Nodejs
- nodemon
- Express
- Nodejs
- 前端:
- React
- react-router
- Material-UI
- react-tap-event-plugin
- React
- 打包工具:
- webpack
- style-loader
- babel-loader
- webpack-dev-server
- babel
- babel-preset-es2015
- babel-preset-react
- babel-preset-stage-1
- webpack
框架目录结构
框架界面
具体技术点
后端Server
- 使用nodejs+express创建后台服务。网上很多教程
- 关键点:
server端的日志输出,把所有请求都通过日志输出
1
2
3
4// 通过使用给express里添加morgan,就可以实现
var logger = require('morgan');
var app = express();
app.use(logger('dev'));实现Server的404异常,利用express的中间件机制原理,实现404找不到页面异常
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// 工具中间件
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
// 业务中间件
app.use('/', index);
app.use('/users', users);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});nodemon实现改动server端代码后,实现自动重新加载
webpack打包配置
高级特性
在写React模块时,为了更加方便编写,使用了一些高级特性:
ES6语法
1
2
3
4
5
6
7
8
9
10// es6的模块化引入
import React, {Component} from 'react';
// es6的类定义
class Master extends Component {
}
// 模块化导出
export default XXX;JSX标记
1
2
3
4
5
6return (
<div>
...
</div>
);class类的成员变量定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 注意:在es6的规范中,并不支持成员变量,static变量直接在class里定义,只能如下定义
class Master extends Component {
constructor(){
this.state = {
navDrawerOpen: false
};
}
}
// 但我们希望使用下面的语法规则
class Master extends Component {
state = {
navDrawerOpen: false
};
}
上面的高级特性,现在主流的浏览器都还不支持,为了使用,我们就需要进行转换:
- webpack
- bable-loader // 用于加载babel
- bable
- babel-preset-es2015 // 转换es6语法
- babel-preset-react // 转换jsx语法
- babel-preset-stage-1 // 转换成员变量语法
- bable
- bable-loader // 用于加载babel
开发环境配置
通过自己搭后台服务与webpack的watch来实现
webpack-dev-server,HotModuleReplacementPlugin实现热更新 --- 推荐方式
webpack-dev-server.config.js的具体配置(webpack-dev-server配置):
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
51const webpack = require('webpack');
const path = require('path');
// 开发期间把www做为了输出目录,不与正式环境情况发生冲突
const buildPath = path.resolve(__dirname, 'src/www');
module.exports = {
entry: [
'webpack/hot/dev-server', // 热修复配置,这个需要一起合并到app.js里
path.resolve(__dirname, 'src/app/app.js') // app的入口
],
output: {
path: buildPath,
filename: 'app.js'
//publicPath: buildPath // 不用特别指定publicPath路径
},
// 这个是webpack-dev-server的运行参数
devServer: {
contentBase: path.resolve(__dirname, 'src/www'),
hot:true, // 热修复
inline: true, // 使用热修复,必须是inline模式
port: 8080 // 创建的服务器的port,自由配置
},
resolve: {
extensions: ['', '.js', '.jsx', '.css', '.json']
},
plugins: [
// 让webpack-dev-server支持热更新
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
"presets": [
"react", // 为了支持jsx的语法
"es2015", // 为了支持es6的语法
"stage-1" // 为了支持class的成员变量与静态变量
]
}
},
{
test: /\.css$/,
loader: 'style-loader!css-loader'
}
]
}
};通过下面命令运行webpack-dev-server,开发环境配置完成,即可实现修改了js文件后,主动推送更新浏览器
1
2
3
4
5
6
7
8// 先在package.json里配置
"scripts": {
"start": "nodemon ./bin/www",
"browser:development": "webpack-dev-server --config client/webpack-dev-server.config.js --progress --colors --inline"
}
// 命令行里运行
npm run browser:development通过这种方式启动的webpack-dev-server后,通过ctrl-z能停掉服务,但无法释放所占用的8080端口号,需求如下操作,kill掉此端口的占用,才能再次启动。
1
2
3
4// 查找端口被哪些服务占用
lsof -i:8080
// kill掉此进程
kill -9 进程pid
前端框架
基于React的开发思路变化
传统开发模式:
React开发模式:
app前端入口
1 | import React from 'react'; |
app的入口职责很简单:
- 路由配置
- 通用处理,如material-ui库里的事件初始化:injectTapEventPlugin();
app的此入口相当于Android里的Application
app的前端路由
在android里,一个界面跳转到另外的界面,是通过协议intent与startActivity()方法来实现跳转,其中的核心实现是由系统自己封装掉了
在前端,界面之前跳转的协议都是URL,再通过window.location.href重新向server请求并加载新页面。
在React的模式下,跳转协议也是URL,但这个URL不用经过server请求,而是重新加载新模块实现,如下图所示:
要想实现此效果,不使用React-Router开源框架,我们的写法为:
1 | var React = require('react'); |
这样实现,也比较容易,但当我们要进行复杂的路由时,就会变的非常麻烦了,所以我们需要使用react-router。
我们的路由配置AppRoutes.js的代码如下:
1 | import React from 'react'; |
更多配置请点击:ReactRouteConfig
Master.js框架的实现
Material-UI理解 官网
Material-UI作用
- 使用其提供的组件,可以开发出与Android原生的Design设计库一致的效果
- 使用其样式及主题,统一所有的控件与界面的风格,方便统一风格切换
- 能方便PC,App的适配,提供的控件及源码里有适配的解决方案
- 对React-Native而言,方便统一PC,M,Android,Ios四端的风格样式
Master.js代码分析
1 | import React, {Component} from 'react'; |