Open lostvita opened 4 years ago
本系列文章计划撰文3篇:
项目地址:github react-admin 系统预览:react-admin system
本系列文章将介绍从零开始搭建一个高可复用的后台架构系统,让每一个人都能轻松搭出自己的后台。系统功能包含登录授权、路由鉴权与组件化,涉及react-router与react-redux的应用。系统最终实现的效果: 本篇介绍的内容包含:
react-router
react-redux
创建一个React App,目测有3到4种方法,这是官网文档的说明,可以移步查看。我们这里主要介绍npx和npm两种方式。
npx
npm
2.1.1 什么是npx? npx是npm@5.2版本新增的命令,如果你的npm版本是高于这个版本的,那么你可以直接使用npx命令了;否则就需要全局安装:npm install -g npx。但个人建议是升级npm:npm install -g npm@next。
npm@5.2
npm install -g npx
npm install -g npm@next
npx的愿景就是:让天下没有难调用的模块。譬如我们项目中集成了Eslint模块,我们想要在命令行调用该模块必须像这样:./node_modules/.bin/eslint --init;有了npx,我们可以直接:npx eslint --init。
Eslint
./node_modules/.bin/eslint --init
npx eslint --init
同时,npx还能避免全局安装,真正的属于用完即走。早先以前,我们使用create-react-app创建react应用,必须要全局安装该模块,react script才可用。现在,通过npx执行npx create-react-app my-app,npx会将create-react-app下载在一个临时目录,并让react script全局可用,用完之后再删除,待下次需要时再重新下载。
create-react-app
react
react script
npx create-react-app my-app
使用npx创建本后台应用:npx create-react-app my-admin
npx create-react-app my-admin
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 init
package.json
npm init <initializer>
npm init react-app my-app
react-app
npm init react-app my-admin
在上一步骤创建应用之后,默认的目录架构像下面这样:
执行npm eject命令会生成config和script两个目录,目录下是与项目相关的webpack配置文件,我们可以根据需要自定义构建配置。运行命令后的目录架构如下:
npm eject
config
script
webpack
其次,我们还可以在一些特殊文件中定义配置信息:新建jsconfig.json文件,并填充如下内容:
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中添加样式表的引用。可以得到如下效果:
scss
index.scss
App.css
index.js
4.1 在components目录新建SideBar.js组件,同时在router目录下新建路由配置文件config.js,这份配置文件由侧边栏跟路由共用:
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字段。
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,将其视作组件:
views
login
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即可)。
Route
Page.js
App.js
在上一章节基础路由部分我们用到了一个组件函数AsyncComponent,它是一个工厂函数,用于异步解析我们的组件定义。在大型应用中,我们会使用很多的异步组件,譬如() => import(/* webpackChunkName: "login" */ 'views/login')是一个异步组件,它返回一个promise,在React里面,我们需要自己写一个工厂函数来解析,让Promise变为React可用的组件。AsyncComponent的逻辑很简单:异步解析和渲染:
AsyncComponent
() => import(/* webpackChunkName: "login" */ 'views/login')
promise
Promise
// 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。
/front/login
/front/404
/front/autherror
/front/approval/undo
以上几个路由属于页面级路由,访问无需授权,在下一篇中我们介绍应用级路由,并实现路由鉴权功能。
一、前言
本系列文章计划撰文3篇:
项目地址:github react-admin 系统预览:react-admin system
本系列文章将介绍从零开始搭建一个高可复用的后台架构系统,让每一个人都能轻松搭出自己的后台。系统功能包含登录授权、路由鉴权与组件化,涉及
本篇介绍的内容包含:
react-router
与react-redux
的应用。系统最终实现的效果:二、React环境搭建
2.1 脚手架环境
创建一个React App,目测有3到4种方法,这是官网文档的说明,可以移步查看。我们这里主要介绍
npx
和npm
两种方式。2.1.1 什么是npx?
npx
是npm@5.2
版本新增的命令,如果你的npm
版本是高于这个版本的,那么你可以直接使用npx
命令了;否则就需要全局安装:npm install -g npx
。但个人建议是升级npm:npm install -g npm@next
。npx
的愿景就是:让天下没有难调用的模块。譬如我们项目中集成了Eslint
模块,我们想要在命令行调用该模块必须像这样:./node_modules/.bin/eslint --init
;有了npx
,我们可以直接:npx eslint --init
。同时,
npx
还能避免全局安装,真正的属于用完即走。早先以前,我们使用create-react-app
创建react
应用,必须要全局安装该模块,react script
才可用。现在,通过npx
执行npx create-react-app my-app
,npx
会将create-react-app
下载在一个临时目录,并让react script
全局可用,用完之后再删除,待下次需要时再重新下载。使用
npx
创建本后台应用:npx create-react-app my-admin
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
2.2 构建配置
在上一步骤创建应用之后,默认的目录架构像下面这样:![image](https://user-images.githubusercontent.com/26505680/66271869-760f5c00-e895-11e9-9931-23765a0e3ae0.png)
执行![image](https://user-images.githubusercontent.com/26505680/66271872-8aebef80-e895-11e9-8678-efc947bdded8.png)
npm eject
命令会生成config
和script
两个目录,目录下是与项目相关的webpack
配置文件,我们可以根据需要自定义构建配置。运行命令后的目录架构如下:其次,我们还可以在一些特殊文件中定义配置信息:新建
jsconfig.json
文件,并填充如下内容:2.3 目录说明
最终我们系统的目录架构如下:
三、基础架构
从主应用入口文件·App.js·开始,根据我们系统后台的布局,用以下代码替换原有代码:
加上样式:在![image](https://user-images.githubusercontent.com/26505680/66271898-eae29600-e895-11e9-90b0-145f799f7b9f.png)
scss
目录新建index.scss
,同时删除根目录的App.css
文件(根据个人习惯选择scss
或其他的预编译语言),并在index.js
中添加样式表的引用。可以得到如下效果:四、侧边导航栏
4.1 在
components
目录新建SideBar.js
组件,同时在router
目录下新建路由配置文件config.js
,这份配置文件由侧边栏跟路由共用:4.2 定义路由配置文件:支持多级嵌套,
routes
与component
不能同级共存,如果存在子菜单,则用routes
字段,否则使用component
字段。4.3 使用递归渲染侧边栏
加上样式之后如下图:![image](https://user-images.githubusercontent.com/26505680/66271957-62182a00-e896-11e9-9b84-266680162613.png)
SideBar.js
中预留两个API留待下篇解说:checkActive
: 根据当前访问路由检测菜单项的收合状态;hasPer
: 检测当前用户是否有菜单项的访问权限,有则渲染,无则跳过渲染;五、基础路由
我们需要借助
react-router
实现几个页面:登录、404以及权限错误。这些属于页面级路由,可以归在views
目录下,这里我们在views
目录新建login
目录作为登录应用、components
目录下新建404.js
和AuthError.js
,将其视作组件:Page.js注册路由:
Route
接口用于注册路由并定义渲染逻辑,Page.js
引入了主应用App.js
文件,因此主入口文件index.js需要引入Page.js
并渲染(将原来的引入App.js
改成Page.js
即可)。六、异步组件
在上一章节基础路由部分我们用到了一个组件函数
AsyncComponent
,它是一个工厂函数,用于异步解析我们的组件定义。在大型应用中,我们会使用很多的异步组件,譬如() => import(/* webpackChunkName: "login" */ 'views/login')
是一个异步组件,它返回一个promise
,在React里面,我们需要自己写一个工厂函数来解析,让Promise
变为React可用的组件。AsyncComponent
的逻辑很简单:异步解析和渲染:如上一顿操作之后,我们访问
/front/login
、/front/404
、/front/autherror
就能分别访问到登录、Page Not Found和Permission Error页面了,访问根路径会被重定向到/front/approval/undo
。以上几个路由属于页面级路由,访问无需授权,在下一篇中我们介绍应用级路由,并实现路由鉴权功能。
项目地址:github react-admin 系统预览:react-admin system