【React系列】手把手帶你擼後臺系統(架構篇)

1、前言

本系列文章計劃撰文3篇:javascript

項目地址:github react-admincss

系統預覽:react-admin systemjava

本系列文章將介紹從零開始搭建一個高可複用的後臺架構系統,讓每個人都能輕鬆搭出本身的後臺。系統功能包含登陸受權路由鑑權組件化,涉及react-routerreact-redux的應用。系統最終實現的效果:node

本篇介紹的內容包含:

  • 腳手架環境搭建與構建配置說明
  • 基礎架構設計
  • 側邊欄實現
  • 初探路由

2、React環境搭建

2.1 腳手架環境

建立一個React App,目測有3到4種方法,這是官網文檔的說明,能夠移步查看。咱們這裏主要介紹npxnpm兩種方式。react

2.1.1 什麼是npx?webpack

npxnpm@5.2版本新增的命令,若是你的npm版本是高於這個版本的,那麼你能夠直接使用npx命令了;不然就須要全局安裝:npm install -g npx。但我的建議是升級npmnpm install -g npm@nextgit

npx的願景就是:讓天下沒有難調用的模塊。譬如咱們項目中集成了Eslint模塊,咱們想要在命令行調用該模塊必須像這樣:./node_modules/.bin/eslint --init;有了npx,咱們能夠直接:npx eslint --initgithub

同時,**npx還能避免全局安裝,真正的屬於用完即走。**早先之前,咱們使用create-react-app建立react應用,必需要全局安裝該模塊,react script纔可用。如今,經過npx執行npx create-react-app my-appnpx會將create-react-app下載在一個臨時目錄,並讓react script全局可用,用完以後再刪除,待下次須要時再從新下載。web

使用npx建立本後臺應用:npx create-react-app my-adminshell

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 構建配置

在上一步驟建立應用以後,默認的目錄架構像下面這樣:

執行 npm eject命令會生成 configscript兩個目錄,目錄下是與項目相關的 webpack配置文件,咱們能夠根據須要自定義構建配置。運行命令後的目錄架構以下:
image

其次,咱們還能夠在一些特殊文件中定義配置信息:新建jsconfig.json文件,並填充以下內容:

{
    "compilerOptions": {
      "baseUrl": "src"  // 編譯根路徑
    }
}
複製代碼

2.3 目錄說明

最終咱們系統的目錄架構以下:

├── config   // 配置相關   
├── script   // 構建腳本 
├── public   // 應用對外目錄 
├── src      // 源代碼 
│   ├── components  // 公共組件
│   ├── font        // 字體相關
│   ├── js          // js庫
│   ├── router      // 路由相關
│   ├── scss        // 樣式表
│   ├── store       // redux相關
│   ├── views       // 頁面應用
│   ├── index.js    // 入口文件
│   ├── Page.js     // 頁面路由入口
│   ├── App.js      // 主應用入口
複製代碼

3、基礎架構

從主應用入口文件·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、側邊導航欄

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 定義路由配置文件:支持多級嵌套,routescomponent不能同級共存,若是存在子菜單,則用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: 檢測當前用戶是否有菜單項的訪問權限,有則渲染,無則跳過渲染;

5、基礎路由

咱們須要藉助react-router實現幾個頁面:登陸、404以及權限錯誤。這些屬於頁面級路由,能夠歸在views目錄下,這裏咱們在views目錄新建login目錄做爲登陸應用、components目錄下新建404.jsAuthError.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便可)。

6、異步組件

在上一章節基礎路由部分咱們用到了一個組件函數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

相關文章
相關標籤/搜索