本文主要是介绍《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管理状态控制头部标题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!