前言javascript
學習總結使用,文章中若有錯誤的地方,請指正。該系列文章主要記錄了搭建一個管後臺的步驟,主要實現的功能有:使用路由模擬登陸、退出、以及切換不一樣的頁面;使用redux實現麪包屑;引入使用其餘經常使用的組件,好比highchart、富文本等,後續會繼續完善。css
github地址:https://github.com/huangtao5921/react-antDesgin-admin (歡迎Star)html
項目展現地址:https://huangtao5921.github.io/react-admin/ java
1、redux簡單介紹react
上一篇文章中react + react-router + redux + ant-Desgin 搭建react管理後臺 -- 處理登陸及默認選中側邊欄(六)咱們已經處理了登陸的路由以及刷新頁面根據路由默認打開並選中側邊欄。目前整個項目麪包屑部分是不能根據路由變化的,接下來處理這個問題。咱們要實現的功能是:當點擊側邊欄的時候,麪包屑能顯示目前我處於哪一個頁面,當在瀏覽器中直接輸入url的時候,也要根據輸入的url顯示正確的麪包屑。git
一、爲何要用redux?github
redux能夠幫助咱們處理共享狀態,就好比咱們的麪包屑部分的狀態實際上是共享的,在側邊欄siderBar裏要改變HeaderBar的麪包屑文字,咱們能夠將狀態提高到父組件,父組件再將數據往下面的子組件傳遞,這樣子組件能夠觸發其父組件中的狀態更改,父組件的狀態改變將更新其餘子組件。可是隨着添加了更多的功能和組件,咱們的這種方法就會變得愈來愈難維護,此時咱們就須要redux來替咱們管理狀態。
redux
二、先簡單介紹一下redux和react-redux:數組
redux:是一個狀態管理庫,其實跟react沒啥關係,能夠用在其餘的框架中;瀏覽器
react-redux:Redux 官方提供的 React 綁定庫,具備高效且靈活的特性,咱們會用到它的Provider和connect方法。
三、這裏根據場景總結一下整個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的改變會致使組件中的狀態發生變化。
2、引入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部分我就再也不描述,具體的代碼能夠看完整的項目。
3、總結:
該項目是描述了我本身是怎麼從0開始搭建一個簡單的管理後臺的,因爲我寫的比較倉促,代碼整理的過程也有不少細節的地方沒有整理到(主要是本身懶),不少原理性的東西也沒有說到,其次該項目還有不少能夠改進的地方,好比:
1.css的處理,能夠引入less或者sass;
2.代碼沒有作分包處理;
3.代碼的結構能夠優化的更好;
4.render函數的更新機制能夠優化,頁面變化時,不須要更新的組件能夠不更新;
5.中間件的使用,使用redux-thunk
......
後續的過程我會慢慢把這個系統完善,有想交流問題的能夠加QQ羣:531947619