前言
學習總結使用,文章中如有錯誤的地方,請指正。該系列文章主要記錄了搭建一個管后台的步驟,主要實現的功能有:使用路由模擬登錄、退出、以及切換不同的頁面;使用redux實現面包屑;引入使用其他常用的組件,比如highchart、富文本等,后續會繼續完善。
github地址:https://github.com/huangtao5921/react-antDesgin-admin (歡迎Star)
項目展示地址:https://huangtao5921.github.io/react-admin/
一、redux簡單介紹
上一篇文章中react + react-router + redux + ant-Desgin 搭建react管理后台 -- 處理登錄及默認選中側邊欄(六)我們已經處理了登錄的路由以及刷新頁面根據路由默認打開並選中側邊欄。目前整個項目面包屑部分是不能根據路由變化的,接下來處理這個問題。我們要實現的功能是:當點擊側邊欄的時候,面包屑能顯示目前我處於哪個頁面,當在瀏覽器中直接輸入url的時候,也要根據輸入的url顯示正確的面包屑。
1、為什么要用redux?
redux可以幫助我們處理共享狀態,就比如我們的面包屑部分的狀態其實是共享的,在側邊欄siderBar里要改變HeaderBar的面包屑文字,我們可以將狀態提升到父組件,父組件再將數據往下面的子組件傳遞,這樣子組件可以觸發其父組件中的狀態更改,父組件的狀態改變將更新其他子組件。但是隨着添加了更多的功能和組件,我們的這種方法就會變得越來越難維護,此時我們就需要redux來替我們管理狀態。
2、先簡單介紹一下redux和react-redux:
redux:是一個狀態管理庫,其實跟react沒啥關系,可以用在其他的框架中;
react-redux:Redux 官方提供的 React 綁定庫,具有高效且靈活的特性,我們會用到它的Provider和connect方法。
3、這里根據場景總結一下整個redux的工作流程(網上隨便找了一張圖片):
Store:唯一數據源(圖書館管理員)
Action:把數據從應用傳到 store 的有效載荷 (用戶說的話)
Reducers:指定了應用狀態的變化如何響應 actions 並發送到 store (電腦)
Components:組件(用戶)
一個用戶去圖書館借書,會先跟圖書館管理員說一句:“有沒有xxx書”,圖書館管理員聽了之后,不知道有沒有xxx書,於是在電腦上搜索一下,搜索到書之后,電腦會將信息返回給管理員,管理員於是告訴用戶有xxx書。這里的用戶跟管理員說的話就相當於Action,管理員就相當於Store,電腦就相當於Reducers,找到書后將信息反饋給管理員。
再結合我們的項目,將整個過程理一遍:首先我們將默認數據存儲在唯一數據源Store中,目前只有面包屑的默認數據,當我們點擊左側邊欄的時候,即在React Componnet中dispatch一個action,Store接受到action之后,會將state和action傳入Reducer,Reducer將state的數據改變然后返回新的State,State的改變會導致組件中的狀態發生變化。
二、引入redux
過程已經描述了一遍,我們要安裝一下三個包,其中redux-devtools-extension是一個redux調試工具,我們也一並安裝了
$ yarn add redux react-redux redux-devtools-extension --save-dev
基於以上的過程,我們來改寫一下我們的代碼,首先,從 src/redux/action/index.js 中寫起:
export const type = { SWITCH_MENU: 'SWITCH_MENU' }; export function switchMenu(menuName) { return { type: type.SWITCH_MENU, menuName } }
改寫 src/redux/reducer/index.js 的代碼:
import { type } from '../action'; const initialState = { menuName: ['首頁'] }; export default (state = initialState , action) => { switch (action.type) { case type.SWITCH_MENU: return { menuName: action.menuName }; default: return state; } }
改寫 src/redux/store/index.js 的代碼,這里的 __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 就是我們剛剛安裝的 redux-devtools-extension 插件,可以方便我們查看state的變化。一會可以在瀏覽器中看到每次state的改變,方便我們的調試。
import { createStore, compose } from 'redux'; import reducer from './../reducer'; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore(reducer, composeEnhancers()); export default store;
改寫 src/index.js 的代碼
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { BrowserRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import store from './redux/store/index'; ReactDOM.render( <Provider store={ store }> <BrowserRouter> <App/> </BrowserRouter> </Provider> , document.getElementById('root'));
此時刷新我們的頁面,沒有報錯說明我們的redux已經引入成功,接下來我們要開始使用redux,點擊側邊欄的時候,我們要派發一個action給Store,因此我們需要在側邊欄組件中修改我們的代碼,找到 src/component/layout/SiderBar.js ,開始修改,紅色部分是改動過的:
import React from 'react'; import { NavLink } from 'react-router-dom' import { Menu, Icon, Layout } from 'antd'; import menuConfig from '../../config/menuConfig'; import logoURL from '../../images/logo.jpeg'; import { connect } from 'react-redux'; import { switchMenu } from '../../redux/action'; const { Sider } = Layout; const { SubMenu } = Menu; class SiderBar extends React.Component { constructor(props) { super(props); SiderBar.that = this; } state = { collapsed: false, menuList: [], defaultOpenKeys: [], // 默認展開 defaultSelectedKeys: ['/'], // 默認選中 }; componentWillMount() { this.handleDefaultSelect(); const menuList = this.setMenu(menuConfig); this.setState({ menuList }); } // 刷新頁面,處理默認選中 handleDefaultSelect = () => { let menuConfigKeys = []; menuConfig.forEach((item) => { menuConfigKeys.push(item.key); }); const pathname = window.location.pathname; const currentKey = '/' + pathname.split('/')[1]; if (menuConfigKeys.indexOf(currentKey) === 1) { this.setState({ defaultOpenKeys: [currentKey], defaultSelectedKeys: [pathname], }); const titleArray = this.selectBreadcrumb(currentKey, pathname); this.props.handleClick(titleArray); } }; // 處理菜單列表 setMenu = (menu, pItem) => { return menu.map((item) => { if (item.children) { return ( <SubMenu key={ item.key } title={ <span><Icon type={ item.icon }/><span>{ item.title }</span></span> }> { this.setMenu(item.children, item) } </SubMenu> ) } return ( <Menu.Item title={ item.title } key={ item.key } pitem={ pItem }> <NavLink to={ item.key } > { item.icon && <Icon type={ item.icon }/> } <span>{ item.title }</span> </NavLink> </Menu.Item> ) }); }; // 導出出面包屑數組 selectBreadcrumb = (currentKey, pathname) => { const titleArray = []; menuConfig.forEach((item) => { if (item.key === currentKey) { titleArray.push(item.title); } if (item.children) { item.children.forEach((sItem) => { if (sItem.key === pathname) { titleArray.push(sItem.title); } }); } }); return titleArray; }; // 點擊側邊欄 handleClick = (item) => { const currentKey = '/' + item.key.split('/')[1]; const pathname = item.key; const titleArray = SiderBar.that.selectBreadcrumb(currentKey, pathname); this.props.handleClick(titleArray); }; // 收縮側邊欄 onCollapse = collapsed => { this.setState({ collapsed }); }; render() { let name; if (!this.state.collapsed) { name = <span className="name">React管理后台</span>; } return ( <Sider collapsible collapsed={ this.state.collapsed } onCollapse={ this.onCollapse }> <div className="logo"> <img className="logo-img" src={ logoURL } alt=""/> { name } </div> <Menu onClick={ this.handleClick } theme="dark" defaultOpenKeys={ this.state.defaultOpenKeys } defaultSelectedKeys={ this.state.defaultSelectedKeys } mode="inline"> { this.state.menuList } </Menu> </Sider> ); } } const mapStateToProps = () => { return {} }; const mapDispatchToProps = (dispatch) => { return { handleClick(titleArray) { dispatch(switchMenu(titleArray)); } } }; export default connect(mapStateToProps, mapDispatchToProps)(SiderBar);
這邊修改后,我們接着改 src/component/layout/headerBar.js 中的代碼,標紅部分是需要更改的地方,接收redux的數據:
import React from 'react'; import { Layout, Menu, Dropdown, Icon, Breadcrumb } from 'antd'; import customUrl from '../../images/custom.jpeg'; import { connect } from 'react-redux'; const { Header } = Layout; class UserInfo extends React.Component { state = { visible: false, // 菜單是否顯示 }; handleMenuClick = e => { if (e.key === 'outLogin') { this.setState({ visible: false }); window.localStorage.removeItem('loggedIn'); this.props.history.push('/login'); } }; handleVisibleChange = flag => { this.setState({ visible: flag }); }; render() { const menu = ( <Menu onClick={ this.handleMenuClick }> <Menu.Item key="outLogin">退出登錄</Menu.Item> </Menu> ); return ( <Dropdown overlay={ menu } onVisibleChange={ this.handleVisibleChange } visible={ this.state.visible }> <div className="ant-dropdown-link"> <img className="custom-img" src={ customUrl } alt=""/> <Icon type="caret-down" /> </div> </Dropdown> ); } } const HeaderBar = (props) => { return ( <Header> <Breadcrumb> { props.menuName.map((item) => { return ( <Breadcrumb.Item key={ item }>{ item }</Breadcrumb.Item> ); }) } </Breadcrumb> <UserInfo history={ props.history }/> </Header> ); }; const mapStateToProps = (state) => { return { menuName: state.menuName } }; export default connect(mapStateToProps)(HeaderBar);
此時點擊我們的側邊欄發現我們的面包屑已經會隨着頁面的變化而變化了。打開我們的控制台,也可以看到state的變化過程:
到目前為止,我們的項目算是基本搭建完了,接下來就是首頁引入highchart以及富文本頁面引入富文本。為了減少篇幅,這2部分我就不再描述,具體的代碼可以看完整的項目。
三、總結:
該項目是描述了我自己是怎么從0開始搭建一個簡單的管理后台的,由於我寫的比較倉促,代碼整理的過程也有很多細節的地方沒有整理到(主要是自己懶),很多原理性的東西也沒有說到,其次該項目還有很多可以改進的地方,比如:
1.css的處理,可以引入less或者sass;
2.代碼沒有做分包處理;
3.代碼的結構可以優化的更好;
4.render函數的更新機制可以優化,頁面變化時,不需要更新的組件可以不更新;
5.中間件的使用,使用redux-thunk
......
后續的過程我會慢慢把這個系統完善,有想交流問題的可以加QQ群:531947619