10.08 「React系列」手把手帶你擼後臺系統(架構篇)

一、前言

鏈接:https://juejin.im/post/5d9b5ddee51d45781b63b8f7

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

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

  • 本篇介紹的內容包含:腳手架環境搭建與構建配置說明
  • 基礎架構設計
  • 側邊欄實現
  • 初探路由

二、React環境搭建

2.1 腳手架環境

創建一個React App,目測有3到4種方法,這是官網文檔的說明,可以移步查看。我們這裡主要介紹npx和npm兩種方式。

2.1.1 什麼是npx?

npx是[email protected]版本新增的命令,如果你的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>

使用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,繼而下載並創建應用。/<initializer>

使用npm init創建本後臺應用:npm init react-app my-admin

2.2 構建配置

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

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

執行npm eject命令會生成config和script兩個目錄,目錄下是與項目相關的webpack配置文件,我們可以根據需要自定義構建配置。運行命令後的目錄架構如下:

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

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

{
"compilerOptions": {
"baseUrl": "src" // 編譯根路徑
}

}
複製代碼

2.3 目錄說明

最終我們系統的目錄架構如下:

├── config // 配置相關 
├──/>├── 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 (


側邊導航欄
/

<header>
Hi, 安歌
/<header>

主體內容

<footer>
Copyright@2019 安歌
/<footer>
/

)
}
}
export default App
複製代碼

加上樣式:在scss目錄新建index.scss,同時刪除根目錄的App.css文件(根據個人習慣選擇scss或其他的預編譯語言),並在index.js中添加樣式表的引用。可以得到如下效果:

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

四、側邊導航欄

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 (

    側邊欄

)
}
}
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 ( // 渲染側邊欄

    { map(this.generateSidebar, this.state.routes) }

)
}
generateSidebar (item) { // 一級nav
return

  • 'sidebar-item-name': true,
    'on': item.active /* 當前菜單展開/收起標識 */
    }) }>

    { item.title }



      { this.generateSubMenu(item.routes) }


  • }
    generateSubMenu (routes) { // 子級nav
    return map(each =>

  • { each.component ? : (
    <fragment>
    'sidebar-item-name': true,
    'on': each.active // 當前菜單展開/收起標識
    }) }>
    { each.name }


      { this.generateSubMenu(each.routes) }

    /<fragment>
    ) }
  • , routes)
    }
    }
    複製代碼

    加上樣式之後如下圖:

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

    • 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> <redirect> } />
    <route>
    <route>
    <route> {
    const isLogin = false // 登錄狀態從redux獲取
    return isLogin ? <redirect> : <login>
    } } />
    <route> } />
    /<route>/<route>/<route>/<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 ? : null
    }
    }
    return AsyncComponent
    }
    複製代碼

    如上一頓操作之後,我們訪問/front/login、/front/404、/front/autherror就能分別訪問到登錄、Page Not Found和Permission Error頁面了,訪問根路徑會被重定向到/front/approval/undo。


    分享到:


    相關文章: