本系列文章计划撰文3篇:javascript
项目地址:github react-admincss
系统预览:react-admin systemjava
本系列文章将介绍从零开始搭建一个高可复用的后台架构系统,让每个人都能轻松搭出本身的后台。系统功能包含登陆受权、路由鉴权与组件化,涉及react-router
与react-redux
的应用。系统最终实现的效果:node
建立一个React App,目测有3到4种方法,这是官网文档的说明,能够移步查看。咱们这里主要介绍npx
和npm
两种方式。react
2.1.1 什么是npx?webpack
npx
是npm@5.2
版本新增的命令,若是你的npm
版本是高于这个版本的,那么你能够直接使用npx
命令了;不然就须要全局安装:npm install -g npx
。但我的建议是升级npm:npm install -g npm@next
。git
npx
的愿景就是:让天下没有难调用的模块。譬如咱们项目中集成了Eslint
模块,咱们想要在命令行调用该模块必须像这样:./node_modules/.bin/eslint --init
;有了npx
,咱们能够直接:npx eslint --init
。github
同时,**npx
还能避免全局安装,真正的属于用完即走。**早先之前,咱们使用create-react-app
建立react
应用,必需要全局安装该模块,react script
才可用。如今,经过npx
执行npx create-react-app my-app
,npx
会将create-react-app
下载在一个临时目录,并让react script
全局可用,用完以后再删除,待下次须要时再从新下载。web
使用npx
建立本后台应用:npx create-react-app my-admin
shell
2.1.2 npm init经历了什么?
在@5.2版本以前,执行npm init
会在当前目录建立一个package.json
文件,而在@5.2及以后的版本,咱们能够经过npm init <initializer>
命令建立一个应用,譬如:npm init react-app my-app
。其本质也是内部调用npx
命令,会自动将react-app
补全为create-react-app
,继而下载并建立应用。
使用npm init
建立本后台应用:npm init react-app my-admin
在上一步骤建立应用以后,默认的目录架构像下面这样:
npm eject
命令会生成
config
和
script
两个目录,目录下是与项目相关的
webpack
配置文件,咱们能够根据须要自定义构建配置。运行命令后的目录架构以下:
其次,咱们还能够在一些特殊文件中定义配置信息:新建jsconfig.json
文件,并填充以下内容:
{
"compilerOptions": {
"baseUrl": "src" // 编译根路径
}
}
复制代码
最终咱们系统的目录架构以下:
├── config // 配置相关
├── script // 构建脚本
├── public // 应用对外目录
├── src // 源代码
│ ├── components // 公共组件
│ ├── font // 字体相关
│ ├── js // js库
│ ├── router // 路由相关
│ ├── scss // 样式表
│ ├── store // redux相关
│ ├── views // 页面应用
│ ├── index.js // 入口文件
│ ├── Page.js // 页面路由入口
│ ├── App.js // 主应用入口
复制代码
从主应用入口文件·App.js·开始,根据咱们系统后台的布局,用如下代码替换原有代码:
import React, { Component } from 'react'
class App extends Component {
render () {
return (
<div className="container"> <section className="sidebar"> 侧边导航栏 </section> <section className="main"> <header className="header"> <span className="username">Hi, 安歌</span> </header> <div className="wrapper"> 主体内容 </div> <footer className="footer"> <span className="copyright">Copyright@2019 安歌</span> </footer> </section> </div>
)
}
}
export default App
复制代码
加上样式:在scss
目录新建index.scss
,同时删除根目录的App.css
文件(根据我的习惯选择scss
或其余的预编译语言),并在index.js
中添加样式表的引用。能够获得以下效果:
4.1 在components
目录新建SideBar.js
组件,同时在router
目录下新建路由配置文件config.js
,这份配置文件由侧边栏跟路由共用:
// Sidebar.js: 侧栏导航组件,侧栏菜单在router/config.js配置
import React, { Component } from 'react'
class SideBar extends Component {
constructor (props) {
super(props)
this.state = {
routes: [] // 路由列表
}
}
render () {
return (
<ul className="sidebar-wrapper"> 侧边栏 </ul>
)
}
}
export default SideBar
复制代码
4.2 定义路由配置文件:支持多级嵌套,routes
与component
不能同级共存,若是存在子菜单,则用routes
字段,不然使用component
字段。
// router/config.js
export default [
{
title: '个人事务', // 页面标题&一级nav标题
icon: 'icon-home',
routes: [{
name: '待审批', // 次级nav标题
path: '/front/approval/undo', // 路由url
component: 'ApprovalUndo' // 路由组件
}, {
name: '已处理',
path: '/front/approval/done',
auth: 'add', // 访问所需权限
component: 'ApprovalDone'
}]
},
// ...
]
复制代码
4.3 使用递归渲染侧边栏
// SideBar.js
import React, { Component, Fragment } from 'react'
class SideBar extends Component {
constructor (props) {
// ...
this.generateSidebar = this.generateSidebar.bind(this)
}
render () {
return ( // 渲染侧边栏
<ul className="sidebar-wrapper">
{ map(this.generateSidebar, this.state.routes) }
</ul>
)
}
generateSidebar (item) { // 一级nav
return <li className="sidebar-item" key={item.title}>
<div className={ className({
'sidebar-item-name': true,
'on': item.active /* 当前菜单展开/收起标识 */
}) }>
<span>
{ item.title }
</span>
</div>
<ul className="sidebar-sub">
{ this.generateSubMenu(item.routes) }
</ul>
</li>
}
generateSubMenu (routes) { // 子级nav
return map(each => <li className="sidebar-sub-item" key={ each.name }>
{ each.component ? <a href={ each.path }>{ each.name }</a> : (
<Fragment>
<div className={ className({
'sidebar-item-name': true,
'on': each.active // 当前菜单展开/收起标识
}) }>
{ each.name }
</div>
<ul className="sidebar-sub">
{ this.generateSubMenu(each.routes) }
</ul>
</Fragment>
) }
</li>, routes)
}
}
复制代码
加上样式以后以下图:
SideBar.js
中预留两个API留待下篇解说:
checkActive
: 根据当前访问路由检测菜单项的收合状态;hasPer
: 检测当前用户是否有菜单项的访问权限,有则渲染,无则跳过渲染;咱们须要借助react-router
实现几个页面:登陆、404以及权限错误。这些属于页面级路由,能够归在views
目录下,这里咱们在views
目录新建login
目录做为登陆应用、components
目录下新建404.js
和AuthError.js
,将其视做组件:
├── components // 公共组件
│ ├── 404.js // not found
│ ├── AuthError.js // permission error
├── views // 公共组件
│ ├── login // 登陆应用
│ │ ├── index.js // 登陆页面入口
复制代码
Page.js注册路由:
// Page.js
import React, { Component } from 'react'
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
import App from 'App'
import AsyncComponent from 'components/AsyncComponent'
const Login = AsyncComponent(() => import(/* webpackChunkName: "login" */ 'views/login'))
const NotFound = AsyncComponent(() => import(/* webpackChunkName: "404" */ 'components/404'))
const AuthError = AsyncComponent(() => import(/* webpackChunkName: "autherror" */ 'components/AuthError'))
class Page extends Component {
render () {
return (
<Router>
<Switch>
<Route exact path="/" render={ () => <Redirect to="/front/approval/undo" push /> } />
<Route path="/front/404" component={ NotFound }/>
<Route path="/front/autherror" component={ AuthError } />
<Route path="/front/login" render={ () => {
const isLogin = false // 登陆状态从redux获取
return isLogin ? <Redirect to="/front/approval/undo" /> : <Login />
} } />
<Route render={ () => <App /> } />
</Switch>
</Router>
)
}
}
export default Page
复制代码
Route
接口用于注册路由并定义渲染逻辑,Page.js
引入了主应用App.js
文件,所以主入口文件index.js须要引入Page.js
并渲染(将原来的引入App.js
改为Page.js
便可)。
在上一章节基础路由部分咱们用到了一个组件函数AsyncComponent
,它是一个工厂函数,用于异步解析咱们的组件定义。在大型应用中,咱们会使用不少的异步组件,譬如() => import(/* webpackChunkName: "login" */ 'views/login')
是一个异步组件,它返回一个promise
,在React里面,咱们须要本身写一个工厂函数来解析,让Promise
变为React可用的组件。AsyncComponent
的逻辑很简单:异步解析和渲染:
// AsyncComponent.js
import React, { Component } from 'react'
export default function asyncComponent (importComp) {
class AsyncComponent extends Component {
constructor (props) {
super(props)
this.state = {
component: null
}
}
async componentDidMount () {
const { default: component } = await importComp()
this.setState({
component: component
})
}
render () {
const C = this.state.component
return C ? <C {...this.props} /> : null } } return AsyncComponent } 复制代码
如上一顿操做以后,咱们访问/front/login
、/front/404
、/front/autherror
就能分别访问到登陆、Page Not Found和Permission Error页面了,访问根路径会被重定向到/front/approval/undo
。
以上几个路由属于页面级路由,访问无需受权,在下一篇中咱们介绍应用级路由,并实现路由鉴权功能。
其余系列文章
项目地址:github react-admin
系统预览:react-admin system