《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

相关文章

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

Python 中 requests 与 aiohttp 在实际项目中的选择策略详解

《Python中requests与aiohttp在实际项目中的选择策略详解》本文主要介绍了Python爬虫开发中常用的两个库requests和aiohttp的使用方法及其区别,通过实际项目案... 目录一、requests 库二、aiohttp 库三、requests 和 aiohttp 的比较四、requ

mysql-8.0.30压缩包版安装和配置MySQL环境过程

《mysql-8.0.30压缩包版安装和配置MySQL环境过程》该文章介绍了如何在Windows系统中下载、安装和配置MySQL数据库,包括下载地址、解压文件、创建和配置my.ini文件、设置环境变量... 目录压缩包安装配置下载配置环境变量下载和初始化总结压缩包安装配置下载下载地址:https://d

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

将Python应用部署到生产环境的小技巧分享

《将Python应用部署到生产环境的小技巧分享》文章主要讲述了在将Python应用程序部署到生产环境之前,需要进行的准备工作和最佳实践,包括心态调整、代码审查、测试覆盖率提升、配置文件优化、日志记录完... 目录部署前夜:从开发到生产的心理准备与检查清单环境搭建:打造稳固的应用运行平台自动化流水线:让部署像

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情