前言
本篇文章基于上篇文章所写,增加了新的页面和功能...,距离上次项目过了挺长一段时间,学习了更多关于react的相关知识,学以致用,将所学运用到实战当中,于是决定继续上次项目,进行改善、优化和增加内容。在本篇文章中我将着重来讲如何使用Redux来管理状态、以及最新的react-hooks。希望对你有所帮助。
项目新增
将上次项目的状态都改成了使用了redux进行状态管理,新增多个详情页和评价页面,至于有多少个完全看数据有多少,组件复用的好处就体现在这了,虽然说是复用的组件,要知道每个详情页是要区分开的,通过redux的数据共享以及状态处理,不仅让不同详情页的内容不一样,交互也区分开来了。还新增了性能优化,以及更多hooks的使用技巧。
效果展示
观看需知:防止视频过长,这里就不过多展示了,仅展示两个详情页,赛事筛选的也只是部分录制,具体可见上篇文章。每个详情页不仅内容会不一样,状态的更新也不会影响其他详情页,比如你在这个赛事详情里点赞或评论了,另一个赛事详情是不会出现的。
gitpage在线浏览效果
576711977.github.io/react-cat/
了解Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。使用Redux的优势有很多,一般大的项目使用redux来管理状态,使用起来非常舒服,不会像使用useState一样传递状态时可能发生的,父传子,子传孙...... 有了redux,它可以帮你处理应用的共享状态,数据被合并到一个集中的位置:store,哪个组件有需要就可以快速去获取想要的数据
在越来越多的人使用redux来集中管理后,react官方向我们提供更方便去获取redux状态的react-redux ,我们先安装这两个包redux和react-redux,然后继续讲解 原理图
redux要想学好,这张图是不可或缺的。
我们先了解这图中的几个模块
react components不用说,我们知道这代表多个组件,注意是多个,如果是一个那还用什么redux呢?
在组件想要通过redux做些什么,那需要明确动作对象,就是你要做什么操作,是什么类型的操作
action
action是动作的对象,对象里包含两个属性:
- type: 值是字符串,必有且仅有的标识属性,动作操作的类型
- data: 数据属性,我们要处理的数据 dispatch是一个函数,如果没有了dispatch,后面的流程都不会存在,它会帮我们把action分发下去,继续完成后续流程。
store
为了集中状态,store只有一个,仓库store将state、action、reducer联系起来,在图中就可以发现,store连接的线最多,是就一个c位,全局掌控者,但它实际上并不干活,什么意思呢,在它接收到action动作对象的时候,它不会加工状态,它就像一个让别做事的老板,它会直接交给reducer去做
reducer
老板就一个,但reducer是个打工人可以有多个(打工人泪奔),每个reducer干不同的事。那reducer做什么事呢
- 用于加工状态,第一次执行时是初始化状态(previousState)
- 加工时,浅比较新旧state,注意这里的浅比较,当state是引用数据类型时,我们需要返回新的对象产生新的state,然后return newState,每个reducer最后统一在一块交给store集中管理
流程走完,我们的组件就可以获取Store里已经处理好的状态了,图中这里是通过getState() 的api去获取,在后面我们增加react-redux中的概念后就能使用更加方便的方法去获取
详情页的redux搭建
reducer和action有多个,本文只拿出了部分来讲解
创建仓库
import { createStore, compose, applyMiddleware } from "redux";
import thunk from 'redux-thunk'
import reducer from './reducers'
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default createStore(reducer,composeEnhancers(applyMiddleware(thunk)))
- 我们使用
createStore
api来创建我们的仓库,传入集合后的reducer - composeEnhancers应用我们的开发者工具,可以更方便在浏览器查看状态(浏览器安装插件redux devtools)
- applyMiddleware():应用上基于redux的中间件(插件库)
- 使用异步中间件来支持异步action,npm install --save redux-thunk
编写action
写action之前,我们先定义要用的常量,可以减少我们的出错
export const CHANGE_CITY_SELECT = 'CHANGE_CITY_SELECT'
export const CHANGE_EVALUATE = 'CHANGE_EVALUATE'
export const CHANGE_EVALUATE_ID = 'CHANGE_EVALUATE_ID'
.....
action分同步和异步,同步action很好理解,异步action是用来处理异步任务的,比如获取接口数据
import * as actionTypes from '../constants'
import { getDetailDataRequest } from '../../api/request'
export const changeDetailData = data => ({ // 同步
type: actionTypes.CHANGE_DETAIL_DATA,
data
})
....
....
export const getDetailData = (id) => { // 异步
return (dispatch) => {
(async () => {
let {data} = await getDetailDataRequest(id)
dispatch(changeEnterLoading(false))
dispatch(changeDetailData(data))
})()
}
}
reducer加工
根据type类型来做不同处理
import * as actionTypes from '../constants'
const defaultState = {
detailData: [],
enterLoading: true,
...
...
...
}
export default (state=defaultState, action) => {
const {type, data} = action;
switch (type) {
case actionTypes.CHANGE_IS_WANT:
return {...state, isWant: data}
...
...
...
default:
return state
}
}
组件关联仓库
在main.jsx中使用react-redux提供的Provider:让所有组件都可以得到state数据 引入我们的actions,connect用于包装 UI 组件生成容器组件
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
)
connect的两个参数
1. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性。mapDispatchToProps:将分发action的函数转换为UI组件的标签属性,下方使用了简写方式
- 组件
import {
getDetailData,
changeDetailData,
} from '../../../redux/actions/detail'
...
function Detail(props) {
const {
detailData,
evaluate,
} = props
const {
getDetailData,
changeEvaluateId
} = props //
...
...
}
export default connect( // 简写
state => ({
detailData: state.detail.detailData,
contestList: state.contest.contestList,
...
}),
{
getDetailData,
changeDetailData,
...
}
)(React.memo(Detail))
功能实现
上篇所涉及的就不再说了(太多了,讲不完),比如详情页的popup弹出层,还有一些其他antd-mobile的组件使用也很简单。
要把页面写漂亮很简单,多使用antd-mobile和一些好看的css样式以及好看的图片视频之类的静态资源就好了。这些也不详细展开讲了
但要完成交互就不是一件简单的事,状态的更新和联系很重要,如何去处理状态,怎样在不同情况下使用不同的状态,redux就显得格外重要
先看一下每部分的效果,再主要讲一下整体的思路和逻辑
上一篇文章有的图也不展示了
详情页面
评论页面
不同详情页的分离
其他详情页
详情页
首先详情页,异步action获取数据,不同详情页数据有些是共同的接口,然后reducer处理,使每个详情页的数据分离,交互状态也交给redux来管理,要注意每个状态的改变情况
比如当第一次进入第一个详情页要请求数据是需要有loading状态效果的,然后如果
- 第二次如果是进入相同详情页,数据不需要重新获取,loading效果也就可以直接不加载
- 第二次如果是进入不同详情页,数据是需要重新筛选获取的,就需要先把之前的组件里的数据给清理掉,再把新请求的数据放进来,而且还要有loading效果。在这里如果控制不好,很容易出现点进去时,会有一刻是之前的详情页然后闪一下才变成需要的数据,或者明明页面已经加载好了,loading效果却还持续了一小段时间才消失
详细流程下载代码浏览
评论页
评价页面要做到在评论后,每个详情页的评论都是互相分开的,可以在评论的时候,设置id值,然后在呈现到页面时进行筛选就行了,详细代码见源代码
其他
性能优化
React的memo:当数据变化时,代码会重新执行一遍,但是子组件数据没有变化也会执行,如果使用memo将子组件封装起来,只有子组件的数据发生改变时才会执行,达到性能优化的效果,useMemo上篇文章已提过
React.memo(Detail)
防抖
搜索 封装一个函数用于防抖,参考神三元大佬的防抖(建议去买他的小册,能学到很多东西),防抖是将多次执行变为最后一次执行
const debounce = (func, delay) => {
let timer;
return function (...args) {
if(timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
clearTimeout(timer);
}, delay);
};
};
export { debounce };
CSSTransition
- 给页面的路由切换带来动画效果
- 来自react-transition-group
- in + 私有的useState(show) 初始时 show false 内存中 useEffect mounted挂载后 浏览器上 show true
- classNames="xxx"
- xxx-enter 立刻
- xxx-exit 卸载,fly-exit-active 卸载进行时
<CSSTransition
in={show}
timeout={300}
appear={true}
classNames="xxx"
unmountOnExit
onExit={() => {navigate(-1)}}
>
<Container >
....
....
</Container>
</CSSTransition>
项目主题
global-style.js
export default {
"theme-color-red": "#f33c39",
"font-color-gray": "#808080",
"font-color-tips": "#c9aa8f",
"font-size-s": "13px",
"font-size-m": "14px",
"font-size-l": "16px",
"font-size-ll": "18px",
....
....
};
css in js 中引入使用
import styled from "styled-components";
import style from '../../assets/styles/global-style';
export const Container = styled.div`
color: ${style["font-color-tips"]};
.....
.....
`
结语
写得有点仓促,后续补充,先点个赞支持一下吧
gitpage在线浏览页面效果 576711977.github.io/react-cat/