《React后台管理系统实战:十》Redux项目实战(一):搭建redux环境、用redux管理状态控制头部标题

本文主要是介绍《React后台管理系统实战:十》Redux项目实战(一):搭建redux环境、用redux管理状态控制头部标题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、搭建环境

恢复原项目src后操作Le119

1.用cmd创建文件、文件夹

在src内建立一个a.bat把以下命令放进去即可

#建立2个文件夹
mkdir redux  #containers(暂不用)#建立5个空文件
#type nul> containers\App.js
type nul> redux\store.js
type nul> redux\reducer.js
type nul> redux\actions.js
type nul> redux\action-type.js#移动app.js到components并重命名为container.js(暂时不要)
#move App.js components\container.js#或建立文件并向其中写入内容
#echo 包含n个action模块的函数> redux\actions.js

2.安装redux及相关环境

cnpm install --save redux react-redux redux-thunk
#开发者工具-dev表示开发环境才有
cnpm install --save-dev redux-devtools-extension或
yarn add redux react-redux redux-thunk redux-devtools-extension

二、基础代码部分

1.redux/store.js

//store:redux核心管理对象
import {createStore,applyMiddleware} from 'redux' //创建store及中间件工具
import thunk from 'redux-thunk' //异步工具
import {composeWithDevTools} from 'redux-devtools-extension' //开发者工具
import reducer from './reducer.js' //根据action处理state函数//创建一个store
export default createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))

2.reducer.js

/*根据老的state和指定的action生成并返回新的state的函数*/
import {combineReducers} from 'redux' //用于合并多个reducer为一个,没有多个reducer则直接导出对应函数即可
import storageUtils from '../utils/storageUtils.js'//用来控制头部显示标题的状态
const initHeadTitle='首页2'
function headTitle(state=initHeadTitle,action){switch(action.type){default:return state}
}//用来管理登录用户的reducer函数
const initUser=storageUtils.getUser() //从从localSorage读取user
function user(state=initUser,action){switch(action.type){default:return state}
}/*导出多个reducer函数
向外默认暴露的是合并产生的总的reducer函数管理的总的state的结构:{ headTitle: '首页',user: {}}*/
export default combineReducers({headTitle,user
})

3.index.js

import React from 'react'
import ReactDOM from 'react-dom'
import {Provider} from 'react-redux'//[1]
import store from './redux/store.js'//[2]
import App from './App'
import memoryUtils from './utils/memoryUtils'
import storageUtils from './utils/storageUtils'// 读取local中保存user, 保存到内存中
const user = storageUtils.getUser()
memoryUtils.user = user//[3]加provider和store
ReactDOM.render((<Provider store={store}><App/></Provider>
),document.getElementById('root'))

效果:npm start 打开网址 http://localhost:3000/home,显示redux工具及,首页2及可
在这里插入图片描述

三、头部标题用redux实现

首先打开:pages\left\index.js(左导航组件)、pages\header\index.js(头部组件)

1.actions.js

import {SET_HEAD_TITLE} from './action-type.js'export const set_head_title=(headTitle)=>({type:SET_HEAD_TITLE,data:headTitle})

2.action-type.js

export const SET_HEAD_TITLE='set_head_title' //设置头部标题

3.reducer.js

[1]-[3]

/*根据老的state和指定的action生成并返回新的state的函数*/import {combineReducers} from 'redux' //用于合并多个reducer为一个,没有多个reducer则直接导出对应函数即可
import storageUtils from '../utils/storageUtils.js'
import {SET_HEAD_TITLE} from './action-type.js'//用来控制头部显示标题的状态
const initHeadTitle='首页'
function headTitle(state=initHeadTitle,action){switch(action.type){//[1]添加据action返回不同数据case SET_HEAD_TITLE:return action.datadefault:return state}
}//用来管理登录用户的reducer函数
const initUser=storageUtils.getUser() //从从localSorage读取user
function user(state=initUser,action){switch(action.type){default:return state}
}/*导出多个reducer函数
向外默认暴露的是合并产生的总的reducer函数
管理的总的state的结构:{headTitle: '首页',user: {}}*/
export default combineReducers({headTitle,user
})

4.pages\header\index.js读取redux的state

import React,{Component} from 'react'
import {connect} from 'react-redux' //[1]引入连接react和redux函数
import './header.less'
import {formateDate} from '../../../utils/dateUtils.js' //时间格式化工具
import memoryUtils from '../../../utils/memoryUtils' //内存中存取用户信息工具 默认导出,不用加花括号
import storageUtils from '../../../utils/storageUtils' //删除localstorage中的用户登录数据
import {reqWeather} from '../../../api/index' //引入接口函数,非默认导出,加花括号import {withRouter} from 'react-router-dom' //用于包装当前组件,使其具体路由的3属性history
import menuList from '../../../config/menuConfig.js' //导入导航配置菜单 import {Modal} from 'antd'
import LinkButton from '../../../components/link-button/index'class Header extends Component{state={curentTime:formateDate(Date.now()), //当前时间格式化后的字符串dayPictureUrl:'', //天气小图标地址weather:'', //天气文字}// 获取路径// getPath=()=>{// }getTitle = () => {// 得到当前请求路径const path = this.props.location.pathnamelet titlemenuList.forEach(item => {if (item.key===path) { // 如果当前item对象的key与path一样,item的title就是需要显示的titletitle = item.title} else if (item.children) {// 在所有子item中查找匹配的const cItem = item.children.find(cItem => path.indexOf(cItem.key)===0)// 如果有值才说明有匹配的if(cItem) {// 取出它的titletitle = cItem.title}}})return title}//异步获取天气getWeather = async () => {//解构天气小图标,天气const {dayPictureUrl, weather} = await reqWeather('徐州')//更新状态this.setState({dayPictureUrl, weather})}// 每过一秒获取一次系统时间getTime=()=>{//定时器函数setInterval()this.intervalId = setInterval(()=>{let curentTime=formateDate(Date.now()) //获取当前时间并格式化为字符串this.setState({curentTime})},1000)}//退出登录loginOut=()=>{Modal.confirm({title: '确定要退出登录吗?',content: '是请点确定,否则点取消',onOk:()=> {//改成前头函数,因为下面要用到this.props.history.replace()console.log('OK');//删除localstorage中登录信息。及内存中登录信息storageUtils.removeUser()memoryUtils.user={}//跳转到登录页面,用替换因为无需退回this.props.history.replace('/login')}//,取消时什么也不做,所以可省略不写// onCancel() {//   console.log('Cancel');// },})}//在第一次render()之后执行一次//一般在此执行异步操作: 发ajax请求启动定时器componentDidMount(){this.getTime();this.getWeather();}/*当前组件卸载之前调用清除定时器,避免其造成警告信息*/componentWillUnmount () {// 清除定时器clearInterval(this.intervalId)}render(){//解构state内的数据const {curentTime,dayPictureUrl,weather} = this.state//获取用户名const username = memoryUtils.user.username// 得到当前需要显示的title//const title = this.getTitle() 去除原来代码//[3]新读headtitle方式const title = this.props.headTitlereturn(<div className='header'><div className='header-top'><span>欢迎,{username}</span>{/* href='javascript:' */}<LinkButton  onClick={this.loginOut}>退出</LinkButton></div><div className='header-bottom'><div className='header-bottom-left'><span>{title}</span></div><div className='header-bottom-right'><span>{curentTime}</span><img src={dayPictureUrl} alt='天气'/><span>{weather}</span></div></div></div>)}
}//[2]把headTitle传给header组件
export default connect(state =>({headTitle:state.headTitle}),{}
)(withRouter(Header))

效果:显示首页,但点其它地方还不能自动改变

在这里插入图片描述

5.pages\left\index.js通过action-reducer写入state

【1】-【4】

import React,{Component} from 'react'
import {connect} from 'react-redux' //【1】引入连接函数
import {Link,withRouter} from 'react-router-dom' //withRouter:高阶函数,用于把非路由组件包装成路由组件
import './left.less'
import logo from '../../../assets/images/logo.png'
import { Menu, Icon } from 'antd';
import menuList from '../../../config/menuConfig.js'
import memoryUtils from '../../../utils/memoryUtils'
import {setHeadTitle} from '../../../redux/actions.js'//【2】引入actionconst { SubMenu } = Menu;class LeftNav extends Component{state = {collapsed: false,};//   控制左侧导航收缩toggleCollapsed = () => {this.setState({collapsed: !this.state.collapsed,});};// 根据配置文件自动写入左侧导航到页面getMenuItem_map=(menuList)=>{// 得到当前请求的路由路径const path = this.props.location.pathnamereturn menuList.map(item=>{if(!item.children){return(<Menu.Item key={item.key}><Link to={item.key}><Icon type={item.icon}/><span>{item.title}</span></Link></Menu.Item>)}else{// 查找一个与当前请求路径匹配的子Itemconst cItem = item.children.find(cItem => path.indexOf(cItem.key)===0)// 如果存在, 说明当前item的子列表需要打开if (cItem) {this.openKey = item.key}return(<SubMenukey={item.key}title={<span><Icon type={item.icon}/><span>{item.title}</span></span>}>{this.getMenuItem(item.children)}</SubMenu>)}})}//判断当前登陆用户对item是否有权限hasAuth = (item) => {const {key, isPublic} = item //取出key,菜单是否是公共的(无需权限也可见)const menus = memoryUtils.user.role.menus //得到对应角色拥有的菜单const username = memoryUtils.user.username //得到当前登录用户名/*1. 如果当前用户是admin2. 如果当前item是公开的3. 当前用户有此item的权限: key有没有存在于menus中*/if(username==='admin' || isPublic || menus.indexOf(key)!==-1) {return true} else if(item.children){ // 4. 如果当前用户有此item的某个子item的权限return !!item.children.find(child =>  menus.indexOf(child.key)!==-1) //!!:强制转换成bool类型值}return false}//getMenuItem用reduce函数重写方便对每一条进行控制getMenuItem=(menuList)=>{const path=this.props.location.pathname //得到当前请求路径return menuList.reduce((pre,item)=>{// 如果当前用户有item对应的权限, 才需要显示对应的菜单项if (this.hasAuth(item)) {if(!item.children){//1.没有子菜单添加:pre.push((<Menu.Item key={item.key}>{/**【4】点击时回调action去reducer更新state */}<Link to={item.key} onClick={()=>this.props.setHeadTitle(item.title)}><Icon type={item.icon}/><span>{item.title}</span></Link></Menu.Item>))}else{//2.有子菜单// 查找一个与当前请求路径,是否匹配的子Itemconst cItem = item.children.find(cItem => path.indexOf(cItem.key)===0)// 如果存在, 说明当前item的子列表需要展开if (cItem) {this.openKey = item.key}// 向pre添加<SubMenu>pre.push((<SubMenukey={item.key}title={<span><Icon type={item.icon}/><span>{item.title}</span></span>}>{this.getMenuItem(item.children)}</SubMenu>))}}return pre},[])}/*在第一次render()之前执行一次为第一个render()准备数据(必须同步的)*/componentWillMount () {this.menuNodes = this.getMenuItem(menuList)}render(){// 得到当前请求的路由路径let path=this.props.location.pathname// 得到需要打开菜单项的keyconst openKey = this.openKeyreturn (<div className='left'><Link to='/home' className='left-header'><img src={logo} alt='logo' /><h1>深蓝管理后台</h1></Link><MenuselectedKeys={[path]}defaultOpenKeys={[openKey]} mode="inline"theme="dark">{/*inlineCollapsed={this.state.collapsed}*/}{this.menuNodes}</Menu></div>) }
}/*用withRouter高阶组件:
包装非路由组件, 返回一个新的组件
新的组件向非路由组件传递3个属性: history/location/match*/
//【3】容器组件connect,把action传给reducer用于改变state,把当前组件包装起来,实现和redux的连接
export default connect(state=>({}),{setHeadTitle}
)(withRouter(LeftNav))

效果:点左导航头自动显示对应标题

在这里插入图片描述

四、问题&修复

1.当刷新页面时,当前不论在哪都只显示“首页”

在这里插入图片描述

pages/admin/left/index.js

【1】处加个if语句判断

//getMenuItem用reduce函数重写方便对每一条进行控制getMenuItem=(menuList)=>{const path=this.props.location.pathname //得到当前请求路径return menuList.reduce((pre,item)=>{// 如果当前用户有item对应的权限, 才需要显示对应的菜单项if (this.hasAuth(item)) {//【1】判断item是否是当前对应的itemif (item.key===path || path.indexOf(item.key)===0) {// 更新redux中的headerTitle状态this.props.setHeadTitle(item.title)}if(!item.children){//1.没有子菜单添加:pre.push((<Menu.Item key={item.key}>{/**点击时回调action去reducer更新state */}<Link to={item.key} onClick={()=>this.props.setHeadTitle(item.title)}><Icon type={item.icon}/><span>{item.title}</span></Link></Menu.Item>))}else{//2.有子菜单// 查找一个与当前请求路径,是否匹配的子Itemconst cItem = item.children.find(cItem => path.indexOf(cItem.key)===0)// 如果存在, 说明当前item的子列表需要展开if (cItem) {this.openKey = item.key}// 向pre添加<SubMenu>pre.push((<SubMenukey={item.key}title={<span><Icon type={item.icon}/><span>{item.title}</span></span>}>{this.getMenuItem(item.children)}</SubMenu>))}}return pre},[])}

Last:附件

项目目录结构
│  App.js
│  index.js
│
├─api
│      ajax.js
│      index.js
│
├─assets
│  └─images
│          logo.png
│
├─components
│  │
│  └─link-button
│          index.jsx
│          index.less
│
├─config
│      menuConfig.js
│
│
│
├─pages
│  ├─admin
│  │  │  admin.jsx
│  │  │
│  │  ├─category
│  │  │      add-cate-form.jsx
│  │  │      index.jsx
│  │  │      index.less
│  │  │      update-cate-form.jsx
│  │  │
│  │  ├─charts
│  │  │  ├─bar
│  │  │  │      index.jsx
│  │  │  │      index.less
│  │  │  │
│  │  │  ├─line
│  │  │  │      index.jsx
│  │  │  │      index.less
│  │  │  │
│  │  │  └─pie
│  │  │          index.jsx
│  │  │          index.less
│  │  │
│  │  ├─header
│  │  │      header.less
│  │  │      index.jsx
│  │  │
│  │  ├─home
│  │  │      index.jsx
│  │  │      index.less
│  │  │
│  │  ├─left
│  │  │      index.jsx
│  │  │      left.less
│  │  │
│  │  ├─product
│  │  │      add-update.jsx
│  │  │      detail.jsx
│  │  │      home.jsx
│  │  │      index.jsx
│  │  │      pictures-wall.jsx
│  │  │      product.less
│  │  │      rich-text.jsx
│  │  │
│  │  ├─role
│  │  │      addForm.jsx
│  │  │      authForm.jsx
│  │  │      index.jsx
│  │  │      index.less
│  │  │
│  │  └─user
│  │          add-form.jsx
│  │          index.less
│  │          user.jsx
│  │
│  └─login
│      │  login.jsx
│      │  login.less
│      │
│      └─images
│              bg.jpg
│
├─redux
│      action-type.js
│      actions.js
│      reducer.js
│      store.js
│
└─utilsconstans.jsdateUtils.jsmemoryUtils.jsstorageUtils.js

这篇关于《React后台管理系统实战:十》Redux项目实战(一):搭建redux环境、用redux管理状态控制头部标题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/965217

相关文章

springboot控制bean的创建顺序

《springboot控制bean的创建顺序》本文主要介绍了spring-boot控制bean的创建顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录1、order注解(不一定有效)2、dependsOn注解(有效)3、提前将bean注册为Bea

Python在二进制文件中进行数据搜索的实战指南

《Python在二进制文件中进行数据搜索的实战指南》在二进制文件中搜索特定数据是编程中常见的任务,尤其在日志分析、程序调试和二进制数据处理中尤为重要,下面我们就来看看如何使用Python实现这一功能吧... 目录简介1. 二进制文件搜索概述2. python二进制模式文件读取(rb)2.1 二进制模式与文本

Django调用外部Python程序的完整项目实战

《Django调用外部Python程序的完整项目实战》Django是一个强大的PythonWeb框架,它的设计理念简洁优雅,:本文主要介绍Django调用外部Python程序的完整项目实战,文中通... 目录一、为什么 Django 需要调用外部 python 程序二、三种常见的调用方式方式 1:直接 im

SpringBoot整合 Quartz实现定时推送实战指南

《SpringBoot整合Quartz实现定时推送实战指南》文章介绍了SpringBoot中使用Quartz动态定时任务和任务持久化实现多条不确定结束时间并提前N分钟推送的方案,本文结合实例代码给大... 目录前言一、Quartz 是什么?1、核心定位:解决什么问题?2、Quartz 核心组件二、使用步骤1

使用Redis实现会话管理的示例代码

《使用Redis实现会话管理的示例代码》文章介绍了如何使用Redis实现会话管理,包括会话的创建、读取、更新和删除操作,通过设置会话超时时间并重置,可以确保会话在用户持续活动期间不会过期,此外,展示了... 目录1. 会话管理的基本概念2. 使用Redis实现会话管理2.1 引入依赖2.2 会话管理基本操作

SpringBoot整合AOP及使用案例实战

《SpringBoot整合AOP及使用案例实战》本文详细介绍了SpringAOP中的切入点表达式,重点讲解了execution表达式的语法和用法,通过案例实战,展示了AOP的基本使用、结合自定义注解以... 目录一、 引入依赖二、切入点表达式详解三、案例实战1. AOP基本使用2. AOP结合自定义注解3.

Prometheus+cpolar如何在手机上也能监控服务器状态?

《Prometheus+cpolar如何在手机上也能监控服务器状态?》本文强调了通过Cpolar这一内网穿透工具,轻松突破Prometheus仅限于局域网访问的限制,实现外网随时随地访问监控数据,教你... 目录前言1.安装prometheus2.安装cpolar实现随时随地开发3.配置公网地址4.保留固定

HTML5的input标签的`type`属性值详解和代码示例

《HTML5的input标签的`type`属性值详解和代码示例》HTML5的`input`标签提供了多种`type`属性值,用于创建不同类型的输入控件,满足用户输入的多样化需求,从文本输入、密码输入、... 目录一、引言二、文本类输入类型2.1 text2.2 password2.3 textarea(严格

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加