前言
学习总结使用,文章中如有错误的地方,请指正。该系列文章主要记录了搭建一个管后台的步骤,主要实现的功能有:使用路由模拟登录、退出、以及切换不同的页面;使用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