《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

相关文章

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3

CSS Padding 和 Margin 区别全解析

《CSSPadding和Margin区别全解析》CSS中的padding和margin是两个非常基础且重要的属性,它们用于控制元素周围的空白区域,本文将详细介绍padding和... 目录css Padding 和 Margin 全解析1. Padding: 内边距2. Margin: 外边距3. Padd

CSS will-change 属性示例详解

《CSSwill-change属性示例详解》will-change是一个CSS属性,用于告诉浏览器某个元素在未来可能会发生哪些变化,本文给大家介绍CSSwill-change属性详解,感... will-change 是一个 css 属性,用于告诉浏览器某个元素在未来可能会发生哪些变化。这可以帮助浏览器优化

CSS去除a标签的下划线的几种方法

《CSS去除a标签的下划线的几种方法》本文给大家分享在CSS中,去除a标签(超链接)的下划线的几种方法,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧... 在 css 中,去除a标签(超链接)的下划线主要有以下几种方法:使用text-decoration属性通用选择器设置:使用a标签选择器,将tex

前端高级CSS用法示例详解

《前端高级CSS用法示例详解》在前端开发中,CSS(层叠样式表)不仅是用来控制网页的外观和布局,更是实现复杂交互和动态效果的关键技术之一,随着前端技术的不断发展,CSS的用法也日益丰富和高级,本文将深... 前端高级css用法在前端开发中,CSS(层叠样式表)不仅是用来控制网页的外观和布局,更是实现复杂交

Python将博客内容html导出为Markdown格式

《Python将博客内容html导出为Markdown格式》Python将博客内容html导出为Markdown格式,通过博客url地址抓取文章,分析并提取出文章标题和内容,将内容构建成html,再转... 目录一、为什么要搞?二、准备如何搞?三、说搞咱就搞!抓取文章提取内容构建html转存markdown

在React中引入Tailwind CSS的完整指南

《在React中引入TailwindCSS的完整指南》在现代前端开发中,使用UI库可以显著提高开发效率,TailwindCSS是一个功能类优先的CSS框架,本文将详细介绍如何在Reac... 目录前言一、Tailwind css 简介二、创建 React 项目使用 Create React App 创建项目