react + react-router + redux + ant-Desgin 搭建react管理后台 -- 引入rudex并处理面包屑(七)

前言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

  

相关文章
相关标签/搜索