React 16.x折騰記 - (1) React Router V4 和antd側邊欄的正確關聯及動態title的實現

前言

一如既往,實戰出真理,有興趣的能夠瞧瞧,沒興趣的大佬請止步於此。javascript

效果圖

  • 基於antdsidebar組件封裝

折騰記的技術棧選型css

  • Mobx & mobx-react(用起來感受良好的狀態管理器)
  • React 16.4.2 (從這個起步,用新不用舊)
  • React Router V4 (如上)
  • antd (版本追求如上 , 阿里出品的UI框架)
  • styled-components (不想寫單獨的樣式文件,用這個是棒棒的,用過都說好)
  • webpack 4.16.5(版本追求如上)

實現思路

實現思路html

  • 自行維護一份靜態路由表
  • 結合路由的history對象的pathanme
  • 在組件渲染完畢的狀況下,再去遍歷路由表,經過setState從新渲染側邊欄
  • 爲何不在組件初始化的時候就設置,那這樣對於404的路由無法控制
    • react-router-dom v4雖然提供了全局404組件,可是history裏面沒有表明404的狀態

實現目標前端

  • 點擊側邊欄的子菜單會改變標題,對應的item也會高亮
  • 直接修改路由,初次加載等會自動展開對應的分組,高亮對應的子項
  • 不匹配的路由不展開和高亮任何

能學到啥vue

我儘可能註釋,而收穫見仁見智了java

個人思路? 個人的代碼姿式? 僅供參考node

實現代碼

基礎版

  • 靜態路由表一份
export const sidebarData = [
    {
        key: 'group0',
        title: {
            icon: 'dashboard',
            text: '數據分析'
        },
        children: [
            {
                key: '1',
                text: '數據監控',
                path: '/dashboard/monitor'
            },
            {
                key: '2',
                text: '數據分析',
                path: '/dashboard/analyze'
            }
        ]
    },
    {
        key: 'group1',
        title: {
            icon: 'play-circle',
            text: '音頻管理'
        },
        children: [
            {
                key: '6',
                text: '聲兮列表',
                path: '/voice/sxlist'
            },
            {
                key: '7',
                text: '回聲列表',
                path: '/voice/calllist'
            }
        ]
    },
    {
        key: 'group2',
        title: {
            icon: 'schedule',
            text: '活動中心'
        },
        children: [
            {
                key: '11',
                text: '活動列表',
                path: '/active/list'
            },
            {
                key: '12',
                text: '新建活動',
                path: '/active/add'
            }
        ]
    },
    {
        key: 'group3',
        title: {
            icon: 'apple-o',
            text: 'APP管理'
        },
        children: [
            {
                key: '16',
                text: '移動交互',
                path: '/appmanage/interaction'
            },
            {
                key: '17',
                text: '回聲列表',
                path: '/test'
            },
            {
                key: '18',
                text: '用戶列表',
                path: '/user/list'
            }
        ]
    },
    {
        key: 'group4',
        title: {
            icon: 'safety',
            text: '安全中心'
        },
        children: [
            {
                key: '21',
                text: '舉報處理',
                path: '/safety/report'
            },
            {
                key: '22',
                text: '廣播中心',
                path: '/safety/broadcast'
            }
        ]
    },
    {
        key: 'group5',
        title: {
            icon: 'user',
            text: '系統設置'
        },
        children: [
            {
                key: '26',
                text: '我的設置',
                path: '/user/setting'
            },
            {
                key: '27',
                text: '用戶列表',
                path: '/user/list'
            }
        ]
    },
    {
        key: 'group6',
        title: {
            icon: 'info-circle',
            text: '平臺設置'
        },
        children: [
            {
                key: '31',
                text: '用戶協議',
                path: '/platform/license'
            },
            {
                key: '32',
                text: '幫助中心',
                path: '/platform/help'
            }
        ]
    }

];

export const groupKey = sidebarData.map(item=>item.key);


複製代碼
  • sidebar
import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom';

// antd
import { Layout, Menu, Icon } from 'antd';
const { Sider } = Layout;
const { SubMenu, Item } = Menu;
import { sidebarData, groupKey } from 'pages/Layout/SidebarData';

// Logo組件
import Logo from 'pages/Layout/Logo';
@withRouter
class Sidebar extends Component {
    constructor(props) {
        super(props);
        // 初始化置空能夠在遍歷不到的時候應用默認值
        this.state = {
            openKeys: [''],
            selectedKeys: [''],
            rootSubmenuKeys: groupKey,
            itemName: ''
        };
    }

    setDefaultActiveItem = ({ location }) => {
        const { pathname } = location;
        sidebarData.map(item => {
            if (item.pathname) {
                // 作一些事情,這裏只有二級菜單
            }
            // 由於菜單隻有二級,簡單的作個遍歷就能夠了
            if (item.children && item.children.length > 0) {
                item.children.map(childitem => {
                    // 爲何要用match是由於 url有可能帶參數等,全等就不能夠了
                    // 如果match不到會返回null
                    if (pathname.match(childitem.path)) {
                        this.setState({
                            openKeys: [item.key],
                            selectedKeys: [childitem.key]
                        });
                        // 設置title
                        document.title = childitem.text;
                    }
                });
            }
        });
    };

    componentDidMount = () => {
        // 設置菜單的默認值
        this.setDefaultActiveItem(this.props);
    };

    OpenChange = openKeys => {
        console.log(openKeys);
        const latestOpenKey = openKeys.find(
            key => this.state.openKeys.indexOf(key) === -1
        );
        console.log(latestOpenKey);
        if (this.state.rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
            this.setState({ openKeys });
        } else {
            this.setState({
                openKeys: latestOpenKey ? [latestOpenKey] : [...openKeys]
            });
        }
    };

    render() {
        const { openKeys, selectedKeys } = this.state;
        const { collapsed, onCollapse } = this.props;
        const SideTree = sidebarData.map(item => (
            <SubMenu
                key={item.key}
                title={
                    <span>
                        <Icon type={item.title.icon} />
                        <span>{item.title.text}</span>
                    </span>
                }>
                {item.children &&
                    item.children.map(menuItem => (
                        <Item
                            key={menuItem.key}
                            onClick={() => {
                                // 設置高亮的item
                                this.setState({ selectedKeys: [menuItem.key] });
                                // 設置文檔標題
                                document.title = menuItem.text;
                            }}>
                            <Link to={menuItem.path}>{menuItem.text}</Link>
                        </Item>
                    ))}
            </SubMenu>
        ));
        return (
            <Sider
                collapsible
                breakpoint="lg"
                collapsed={collapsed}
                onCollapse={onCollapse}
                trigger={collapsed}>
                <Logo collapsed={collapsed} />
                <Menu
                    subMenuOpenDelay={0.3}
                    theme="dark"
                    openKeys={openKeys}
                    selectedKeys={selectedKeys}
                    mode="inline"
                    onOpenChange={this.OpenChange}>
                    {SideTree}
                </Menu>
            </Sider>
        );
    }
}

export default Sidebar;


複製代碼

collapsed,onCollapse這些是控制側邊欄縮小的,接受的是外部的propsreact

拓展版思路

觸類旁通,一樣咱們同在能夠在靜態路由添加鑑權,好比某個路由僅限某些用戶訪問!webpack

這樣鑑權機制能夠作到很細緻化,可是對應的判斷邏輯也會多起來,看業務改了git

也能夠維護過渡效果,添加對應的字段,而後每次訪問不一樣URL的時候更改過渡效果

以上的都須要依賴狀態管理器,來維護,由於涉及到不一樣組件的通信,mobx也能夠,redux也行,蘿蔔青菜各有所愛

答疑

  • 小夥伴留言說要看個人目錄如何構建的,其實和常規的搭建差很少,以下

如何生成漂亮的目錄樹

用了tree命令,而後我作了個alias組合,方便生成

alias gdtree="tree -I 'node_modules|dist|.git|.vscode|.DS_Store|.idea' -L 2 -a"

我直接寫到環境文件裏了, -L就是遍歷的層級, -a是全部文件(包括隱藏), -I是正則忽略

├── .babelrc  # babel配置
├── .browserslistrc #瀏覽器的兼容範圍
├── .editorconfig # 基礎規範
├── .eslintignore # eslint忽略
├── .eslintrc # eslint 配置
├── .gitignore
├── .postcssrc.js # postcss配置
├── .prettierrc # 格式化代碼的配置文件
├── README.md
├── build  # webpack的構建目錄
│   ├── webpack.base.config.js # 通用的webpack配置,能夠理解爲common,開發和生產都依賴,好比插件等
│   ├── webpack.development.js  # 開發模式專有,熱更新,反向代理啥的
│   └── webpack.production.js # 儘量的壓縮切割,抽離樣式爲CSS文件什麼的
├── jsconfig.json # 用來映射webpack alias 的,這樣vscode下才能智能提示alias的路徑
├── package.json
├── public
│   ├── favicon.png
│   └── index.html
├── src
│   ├── App.css
│   ├── App.js # 根組件
│   ├── PrivateRoute.js # 私有路由,對Route的封裝
│   ├── assets # 靜態資源
│   ├── components # 通用組件
│   ├── index.js # webpack的主入口
│   ├── pages # 頁面組件
│   ├── services # api的封裝,以及彙總地方
│   ├── store # 狀態管理
│   └── utils # 公用的代碼片斷,好比一些函數什麼的
├── tests # 存放jest單元測試的目錄
│   └── union
└── yarn.lock

複製代碼

總結

公司最近打算重構整個後臺管理系統,把老的兩個系統整合在一塊兒。

由於小做坊只有我一個前端開發,單打獨鬥的好處(bei shang)就是技術選型能夠本身把握,

說作就作,用最新的webpack4 搭了個架子,開始折騰(由於比較新,更新依賴很容易出問題)。

等項目完畢再把腳手架放出來,估計webpack5都出來了..

有人確定會說,官方有現成的antd pro爲嘛不用,我看了跟dva高度結合,不喜歡,那就本身搭架子。

以前用vueng都是整個系統佈局本身寫一遍,此次試試用現成的側邊欄來實現

有不對之處請留言,看到會及時修正,謝謝閱讀.

相關文章
相關標籤/搜索