一、论管理系统的模块化
不管是给别人做项目,还是自己家的项目,一个网站、app总少不了一套管理系统。
管理系统是个很特殊的东西,功能很多,对界面要求不高,可复用性特别好,尤其适合做成模块化的框架来使用。
下面是一个典型的管理系统界面:
【图一、管理系统界面】
在图上已经圈出了管理系统的几个大模块:
- 用户信息、logo等所在的顶部信息栏
- 左侧的导航栏
- 主要展示区
根据React的思想,将整个网站视为一个app,现在三个大组件就很明确了。整个系统的流程就应当是点击导航栏之后,更换主要展示区所加载的组件实现不同的功能模块。
接下来就是进一步的细化主展示区的模块
【图二、主展示区模块示意图】
管理系统的数据展示也就是几种主要方式,每次写起来都要复制粘贴一大堆的html代码导致很麻烦。进行模块化的处理之后,可以将主要精力放在对数据的处理上,而忽略界面的实现细节。大幅度提升之后的工作效率。
二、相关技术
与之前粗浅的React.js入门知识比起来,这次实践中了解到了以下相关技术:
- Javascript ES6
- Webpack
- React-router
随着ES6标准的日益流行,现在前端的应用也愈发广泛。相比起之前的javascript来说,ES6标准更趋近于java的写法,对于我这种后端程序猿更为友好和亲切。
Webpack一直是React优先选择的前端模块化工具之一。可以将js、css甚至是图片资源打包到同一个js文件中。一来方便控制,二来通过减少网络请求次数以提高网站的加载速度。
React-router是React官方的也是目前唯一可用的路由库。通过此库即可实现“点击导航连更换主展示区组件”的路由功能。
三、编写组件
1 总体框架
废话不多说,直接上代码:
import React from 'react';
import ReactDOM from 'react/lib/ReactDOM';
import { Router, Route, hashHistory,Link } from 'react-router';
import Topnav from './topnav.js';
import Navbar from './navbar.js';
import {EditNormalUser,NormalUser,AdminUser,AddAdmin} from './userComponent.js';
class IndexWrapper extends React.Component {
render () {
return (
<div className="wrapper preload">
<header className="top-nav" id="top-nav">
<Topnav />
</header>
<aside className="sidebar-menu fixed" id="navbar">
<Navbar />
</aside>
<div className="main-container" id="main">
{this.props.children}
</div>
</div>
)
}
}
class Main extends React.Component{
render(){
var router = (
<Router history={hashHistory} >
<Route path="/" component={IndexWrapper}>
<Route path="/normal" component={NormalUser}/>
<Route path="/admin" component={AdminUser}/>
<Route path="/admin/add" component={AddAdmin}/>
<Route path="/normal/edit/:uid" component={EditNormalUser}/>
</Route>
</Router>
);
return router;
}
};
复制代码
Router与Route就是两个属于React-router库的组件。是实现路由功能的关键。 hashHistory是用来维护浏览器history的方式,另一种实现是browserHistory。 对应于/normal路径,hashHistory的方式生成的URL如下:
http://localhost:8080/admin.html#/normal?_k=bptt31
复制代码
browserHistory则会跳转到如下路径:
http://localhost:8080/normal
复制代码
相比之下,hashHistory的方式对后端代码影响较小,故在此选择了hashHistory。
Router维护了一个路由的上下文。根据约定呢,在Router下只能有一个元素,在此对应的就是根路径的路由"/",对应的组件则是IndexWrapper,对整体框架的简单包装。
在React中,有个很重要的属性:this.props.children,代表此元素中的子元素。在Main组件中可见对于根路径的路由,对应的组件是IndexWrapper,其中除了顶部的Topnav和左侧的Navbar组件之外,就使用了this.props.children属性,根据路由加载不同的主展示区组件使用。
2、导航
本系统内的导航栏设计为最多二层的结构。包括两种组件:
- 单层的导航栏SingleNav
- 双层的导航栏MultiNav
在数据上,使用数组来进行填充,样例如下:
navtree : [
{
name:"首页",
component:"./home"
},
{
name:"用户管理",
subnav:[
{
name:"普通用户管理",
component:"/normal"
},{
name:"管理员管理",
component:"/admin"
},
]
}
]
复制代码
而对应于数组的处理循环如下:
var navs = [];
for(var i=0;i<this.state.navtree.length;i++){
var nav = this.state.navtree[i];
if(!nav)
continue;
if(nav.subnav){
navs.push(<MultiNav key={"nav-multibar-"+i} nav={nav} ></MultiNav>);
}else{
navs.push(<SingleNav key={"nav-singlebar-"+i} nav={nav} ></SingleNav>);
}
}
复制代码
实际上呢,component指向的是路由的路径,名称什么的是一个历史遗留问题^_^
在导航这里呢,又涉及到一个路由上很重要的组件:Link组件
<Link to={this.props.nav.component} activeClassName="active">
复制代码
Link组件会被编译为一个a标签,to则被编译为href属性,指向指定的url。同时解决了一个选中状态的问题,当被选中时会自动添加类active,这也是前端常用的选中状态类。
要注意的是,Link必须属于某一个路由上下文中。也就是在Router组件之内。在最初的时候并没有使用Router对整个页面进行包裹,导致报错提示Link不再RouterContext之中,编译出href也为空。
3、界面UI组件案例
将UI组件化是节约日后工作量的重头戏,下面就以一个简单的例子来说明,此处并没有什么新的技术加入,所以直接上代码:
export class SmartBox extends React.Component{
render (){
var options = [];
if(this.props.option){
if(this.props.option.reload){
options.push(
<a href="#" className="widget-refresh-option" onClick={this.props.option.reload}>
<i className="fa fa-refresh"></i>
</a>
)
}
if(this.props.option.add){
if(typeof add == "function"){
options.push(
<a href="#" className="widget-refresh-option" onClick={this.props.option.add}>
<i className="fa fa-plus"></i>
</a>
)
}else{
options.push(
<Link to={this.props.option.add}>
<i className="fa fa-plus"></i>
</Link>
)
}
}
}
return (
<div className="padding-md">
{this.props.index}
<div className="smart-widget">
<div className="smart-widget-header">
{this.props.title}
<span className="smart-widget-option">
<span className="refresh-icon-animated">
<i className="fa fa-circle-o-notch fa-spin"></i>
</span>
{options}
</span>
</div>
<div className="smart-widget-inner">
<div className="smart-widget-body">
{this.props.children}
</div>
</div>
</div>
</div>
)
}
}
复制代码
这就是标准的管理系统主模块框架。包括了顶部的面包屑导航和内容的外框包围。两个可选按钮:刷新,添加
此处同样使用了this.props.children属性来添加子节点。
组件的实际使用如下:
render (){
var index = (
<ul className="breadcrumb">
<li>用户管理</li>
<li>管理员</li>
<li>列表</li>
</ul>
);
return (
<SmartBox title="用户信息列表" index={index}
option={{reload:this.loadPage.bind(this),add:"/admin/add"}}>
<DataTable index={this.state.index} data={this.state.data} buttons={this.state.buttons}/>
<Pager pagecount={this.state.pagecount} last={this.state.last} callback={this.loadPage.bind(this)} />
</SmartBox>
)
}
复制代码
在此案例中使用了SmartBox,DataTable和Pager三个UI组件,实现了一个标准的数据列表。
四、项目打包
最后的工作则是使用webpack对此项目进行预编译和打包,在这里需要安装nodejs以使用npm工具。
首先要安装webpack:
npm install webpack -g
复制代码
然后是对webpack进行配置,配置文件如下:
var webpack = require('webpack')
module.exports = {
entry: './application.js',
output: {
filename: '../main.js'
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: "babel",
query: {
presets: ['react', 'es2015']
}
}
]
}
}
复制代码
这个配置文件嘛,他就做了三件微小的工作:
- 指定入口
- 指定输出文件
- 指定js文件的加载器。
最后呢,我先执行了npm install,然后跑了webpack,就打包成了一个main.js文件。
在html中只需要引入main.js文件即可,无需再引入React.js相关的文件。
当然如果愿意,可以将网页中所有的资源都用这种方式引入。
五、结束
一个简单的web页面模块化工作基本就此完成。
随着现在web技术的飞快发展,前端的工程化、模块化已经是大势所趋。不再像以前一样随便写写html和css,js,用几行jquery就能完成工作了。
现在流行的组件化框架,包括react.js,angular.js,vue.js等,都是这种趋势的产物。虽然作为一个主攻后端的工程师,对于前端技术必要的了解和掌握也是必不可少的。