不看攻略手写一个react+redux后台管理系统
B端项目 直接用的ANTD,肯定没那么好看
- 关于redux
想要获取redux中的数据,写在connect的state和方法参数中,调用:this.props.参数/方法名
容器组件想要冲redux中取值。state=>{一般和后面是一样的:state.reducer的Index中定义的名字} ,方法通过action引入
写在class组件中的参数,需要是表达式 state={a:***}, 调用:this.state.a
2021.5.21
测试服务器好用 开始写代码
启动服务器&数据库
- cmd 服务器所在目录,看package.json的要求 npm start
- 导入数据库
脚手架使用
-
create-react-app content_manage
-
删掉脚手架里没用的东西
-
建一个git仓库,上传内容
-
public中存一些静态资源,静态的index界面,之后所有的JS页面都渲染到这个index上。在CSS内存入reset,静态页面引入即可,
-
src文件夹
- api: 存放二次封装的axios
- component: 存放无业务逻辑的UI组件(不能和redux产生交互)
- config:存配置
- containers:所有的容器组件(当然他们也是路由)
- redux: reducers(操作state的方法) action_creators(做什么) store(redux的主要组件) action_type(防止写错)
-
app.js 登录和注册路由
-
index.js 渲染app.js,因为用的是redux 所以用provider包裹
路由定义
- app中是两个主要的路由,login & admin 如果不写默认跳转到home
- 写静态的login,jsx中引入静态的图片要用import
安装依赖
- yarn add react-router-dom(想用路由 必须引入)
- yarn add redux
- npm install --save react-thunk
- npm install --save react-redux // 第一步引入会出错
- npm install --save-dev redux-devtools-extension // 插件依赖
使用Antd
-
yarn add antd
-
antd的修改和按需引入: 看官网文档即可 更改较快
-
antd按需要引入 改配置
-
路由选项卡 Switch包裹
-
Switch外必须有路由器 一般写在index里 可以选择BrowserSouter/HashRoute
-
antd的提交和事实验证方法的编写方法 在成功的方法里发送ajax请求,失败的直接弹出验证
-
自定义校验写在rules里即可!!看文档啊看文档,自己试不如看文档
————————————————————————————
2021.5.22
redux环境搭配
-
为了简洁:把有redux属性的方法UI组件和容器组件写在一起 都写在containers里(既有UI又有容器即connect)
-
新建redux文件夹 新建 store.js reducers(操作state的方法) action_creators(做什么) action_type.js
reducers里写入一个汇总reducer的index文件
-
index里给路由器外包一个顶级组件provider,传store过去
注意暴露方式: 如果是多个函数 不能默认暴露
-
目前login还是UI组件,引入connect 改写为容器组件
一个干净的组件要和redux产生交互 需引入connect 和 action_creators 然后用connect连接action和原组件
开始发请求
-
yarn add axios
-
登录 post 请求
-
出现了!**跨域问题 **
在package.json 添加 “proxy”: “http://localhost:5000” 记得把所有发请求的地址改成3000 就是项目的端口
-
POST请求如何让请求体获得参数,POST请求写成urlencorded才能接收到,后期加一个请求拦截器吧
https://www.cnblogs.com/yiyi17/p/9409249.html
axios的POST请求默认将参数以JSON格式发送给后端
(1)更最简单的解决方式,直接改后台:
//引入body-parser模块 const bodyParser = require('body-parser') //引入中间件,解析JSON格式字符串 router.use(bodyParser.json()); //___________________________ //express也自己封装了一个这个方法,可以不引入新模块 router.use(express.json())
(2)使用querystring
import qs from 'querystring' axios.post('http://localhost:3000/login/',qs.stringify({username,password}))
————————————————————————————
5.23
将项目中所有请求封装到一起
-
新建API文件夹,汇总所有请求并向外暴露
-
axios方法返回一个Promise实例需要自己手动处理成功和失败的回调,为精简写方法,将其改为Async和await
-
使用await只能获得成功的回调,trycatch可以获得失败的回调。但是为了思路更清晰,加入拦截器,不需要catch捕获错误,如果出错直接走响应拦截器的error
-
配置超时 axios.create 加入请求拦截器&响应拦截器(直接去Axios库看格式)
请求拦截器:把传过来的POST请求中是JSON格式的数据用querystring转为urlencorded格式
响应拦截器:如果出错,用ANTD的message组件弹出错误并终端promise链,如果成功返回response
在请求+响应拦截器中加入进度条 用NProgress这个库就可以
-
配置项目的默认路径 新建config文件夹 写一个入口文件
-
改服务器 让服务器返回一个token,作为登录令牌
-
把token 和 user 作保存到redux的state里(用redux的工具查看状态) 这些方法都存在state里,this.props.XXXX
注意token保存的位置,这个直接影响后期如读取内容
-
先保存Token到admin之后 再跳转页面
————————————————————————————
5.24
七天免登录
-
admin中读取是否登录的状态,如果没登录直接跳转login 跳转最好用Redirect
如果没登录必须return一下,不然render会出错
-
服务器返回的登录内容要保存起来,存到localstorge 写在action里
localStorage.setItem(‘user’,JSON.stringify(value.user))
将状态从localStorage更新到login_reducer中,这样就在state中可以和login页面交互
-
从state中读Login状态,如果已经登录了直接跳转到admin
————————————————————————————
5.25
退出登录
- 在admin 页面退出登录,删除localstorage和redux中保存的信息
装饰器语法
-
connect函数是个高阶函数,所以使用装饰器语法处理装饰器语法
-
安装装饰器:
npm install save- @babel/plugin-proposal-decorators
-
修改配置文件 config-overrides.js
const { addDecoratorsLegacy} = require('customize-cra');
module.exports = override(
addDecoratorsLegacy()
);
-
写法
//@connect方法传入state和action方法之后,传入Admin组件 @connect( state => ({userInfo:state.userInfo}), {deleteUserInfo:createDeleteUserInfoAction} ) class Admin extends Component{} // 不能把暴露和组件定义写在一行 export default Admin
如何鉴别用户是否登录
-
cookie 保存session-ID
-
cookie-session 保存敏感数据
-
token(如果将cookie导出,可以模拟登录状态) 过滤器先看token的格式符不符合
之后所有的操作都需要携带token验证自己的登录状态,因此只要发送请求,就要将redux中的token读取出来,因此写在Axios的请求拦截器上,如果有token ,把token加在请求头上。在token前加一个格式前缀,方便过滤器校验
config.headers.Authorization = ‘sqn’+token
-
token过期之后,自动返回登录页面。逻辑完成在响应拦截器里 (store.dispatch就是在和redux交互)
写Admin的整体布局
- 用ANTD的布局
- 拆组件,组件都和REDUX交互的都是容器组件
头部布局
-
上下两部分+静态界面
-
屏幕全屏:screenfull 库
npm install screenfull 事件监听绑定在componentDidMount下 一挂在就监听
-
浏览器全屏的问题目前解决不了
-
从redux中获取用户名
- 从redux中取内容 state.XXX store.getState().userInfo
- 从穿过的state中取内容:this.props.XXXX
-
头部退出登录:加一个confirm提问,是否确定退出,注意this想要传递要不然就提前保存this 否则就用箭头函数
-
头部时间:
state中保存时间,渲染完成后开定时器每一秒更新一下
npm install dayjs 写法: dayjs().format(’{YYYY} MM-DDTHH:mm:ss SSS [Z] A’)\
state={ date:dayjs().format('YYYY年 MM月DD日 HH:mm:ss') } componentDidMount(){ setInterval(()=>{ this.setState({date:dayjs().format('YYYY年 MM月DD日 HH:mm:ss')}) },1000) }
当切换页面时,即头部卸载的时候,要把定时器停掉,写在willUnmount
-
头部天气:
请求百度的天气接口,出现了跨域问题,用jsonp发请求解决跨域问题
npm install jsonp
jsonp的请求结果的获取是异步的。使用Promise,失败了中断promise链,成功返回一个resolve(XXXX)
要把回调函数带回来的值交给外层方法的时候,考虑用promise,不然无法return返回值,回调和主线程是异步执行的,所以用promise,返回一个promise对象,这样在需要用的地方用await去接收
//获取天气信息(百度接口,使用jsonp的方式请求)
export const reqWeather = ()=>{
return new Promise((resolve,reject)=>{
jsonp(`http://api.map.baidu.com/telematics/v3/weather?location=${CITY}&output=json&ak=${WEATHER_AK}`,(err,data)=>{
if(err){
message.error('请求天气接口失败,请联系管理员')
return new Promise(()=>{})
}else{
const {dayPictureUrl,temperature,weather} = data.results[0].weather_data[0]
let weatherObj = {dayPictureUrl,temperature,weather}
resolve(weatherObj)
}
})
})
}
将请求来的天气信息维护进state,页面动态展示state信息
<img src={weatherInfo.dayPictureUrl} alt="天气信息"/>
{weatherInfo.weather} 温度:{weatherInfo.temperature}
使用withRouter
让非路由组件使用路由组件的API,动态获取路由名称,{this.props.location.pathname}
header不是路由组件,就是将非路由组件渲染成路由组件(这也是个高阶组件)
从路径中对应到Title
循环遍历,找path和配置对象中的key有没有一致的情况
getTitle = (()=>{
let pathKey = this.props.location.pathname.split('/').reverse()[0]
let title = ''
menuList.forEach ((item)=>{
if(item.children instanceof Array){
let res = item.children.find ((childItem)=>{
return childItem.key == pathKey
})
if(res) title =res.title;
}else{
if(item.key ==pathKey) title = item.title
}
})
// return title;
// 直接维护到class中的state里 完整写法:this.setState({title:title})
this.setState({title})
})
-
当想要获得title的时候要调用getTitle方法,因为并无点击事件等调用,需要title的时候调用方法为:getTitle()
注意加括号才能调用
————————————————————————————
5.26
侧边栏
-
静态布局:Antd引入即可,引入静态资源注意不能到src外(脚手架的规定),所以在src下建立一个static文件夹
-
引入react-router-dom 中的Link 做自动切换
-
自动生成菜单,写成一个递归的方法 ,这样多级菜单也可以直接动态渲染
createMenu=(target)=>{ return( target.map((item)=>{ if(!item.children){ return( <Item key={item.key} icon = {iconConfigMap[item.icon]}> <Link to={item.path}> <span>{item.title}</span> </Link> </Item> ) }else{ return( <SubMenu key={item.key} icon = {iconConfigMap[item.icon]} title={item.title} > {this.createMenu(item.children)} </SubMenu> ) } } ) ) }
问题:如何动态渲染组件
jsx中无法用模板字符串读取组件,只能将Icon的配置写在JSX文件中
// left_nav.jsx const iconConfigMap = { 'HomeOutlined':<HomeOutlined />, } return( icon = {iconConfigMap[item.icon]} ) // config.js export default[ { title: '首页', // 菜单标题名称 key: 'home', // 对应的path icon: 'HomeOutlined', path: '/admin/home'//对应路径 }]
-
使用withrouter动态获取路径
defaultSelectedKeys={this.props.location.pathname.split('/').reverse()[0]} //默认选中
pathname获得路径,拆分之后翻转数组拿到第一个就是之前的最后一个
这样刷新页面还会停留在原页面,不会按照默认选中跳转到别处
-
选中子菜单的时候,刷新之后默认打开父级菜单
defaultOpenKeys={this.props.location.pathname.split('/').splice(2)} //默认打开
从路径中获取Title的问题
-
由于一直从路径中获取的Title,再加上Home页面开启了一个定时器用来实现秒表,所以每秒钟都会重新渲染页面,为了优化这个问题,我们要把title保存在redux中,如果直接存在state里,并在didUpdate方法中调用getTitle(),会出现挂载的时候的死循环,更新state—挂载—更新state,所以 只能写在redux里。为leftNav中的Item添加点击事件,每次点击将所在的title存入redux
-
重复一下流程:
-
给left_nav中的item加点击事件,定义点击事件,在点击事件中与redux完成通信
-
要通信,把left_nav改写为容器组件,引入connect,connect中存在状态和方法两个参数
-
既然有方法参数,那么就要引入action,新建action文件,写action文件自然要改写Action-types
-
写对应的reducer, reducer写更新state的方法,但是写完记得去index内汇总
-
将一开始的点击事件和redux中定义的方法合并一下,onClick事件不能写小括号,但是需要传参,所以用箭头函数再包一层
<Item key={item.key} icon = {iconConfigMap[item.icon]} onClick={()=>{ this.props.saveTitle(item.title) }}>
-
然后记得给引用的位置改为容器组件,通过redux中connect组件将数据更新至state ,通过this.props.参数 完成渲染
-
最后要完成刷新还能保存路径的逻辑,在getTitle函数中,不返回title,而是将title维护到class的state中的title属性中,注意!不是connect中的state,是类中的是state,connect中的是redux中的值。
-
在didMount方法中调用getTitle(),注意!如果是在didUpdate中调用会出现死循环(见上)
-
渲染的时候,redux中的title有值,渲染这个title,若为空则说明刚刚刷新,去class中的state中获取计算出来的title,因为每次刷新会执行didMount,就是redux有读redux效率高,redux没有就去路径中计算title,存入state
{this.props.title||this.state.title}
-
————————————————————————————
5.29 摸鱼了两天
Category页面
- antd的card+ table写静态页面
- 开始交互,发一个get请求,页面一挂载就开始发送请求,但是不要给生命周期钩子加async,所以在外部定义一个异步函数,在钩子里调用
- 给每个数据加一个key 组件库要求有key,数据库存的是ID。当然可以直接改Table组件的属性!淦!
- 如果想动态给点击窗口起名字,定义一个变量存入class的state中,然后点击事件更改这个state的值,在给属性赋值的时候直接从state身上读取
- 关于Form表单的一些修改,如 formRef = React.createRef(); <Form ref={this.formRef} > 还是仔细看文档比较好
- ANTD中 组件的有一些奇奇怪怪的设定 自己尝试吧。如果想要修改就
————————————————————————————
关于警告
-
scheduler.development.js:298 [Deprecation] SharedArrayBuffer will require cross-origin isolation as of M91, around May 2021. See https://developer.chrome.com/blog/enabling-shared-array-buffer/ for more details.
- 解决方案
yarn upgrade react --latest yarn upgrade react-dom --latest