前言
各位看官们我胡汉三又回来了,最近学习了神三元大佬 的开源项目云音悦。经过这一段时间的学习,React 全家桶基本上算是熟悉了,就用从本次学习学到的Redux
结合React Hooks
完善了起点移动端项目。这篇文章主要分享一下Redux
和React Hooks
的使用方法、本次项目优化的地方和新增的功能,希望对看官们有所帮助。 第一篇还未没看的朋友不要忘记点击这里哟!
一、项目的优化
1.1 axios重新封装
本次项目对axios采用单例设计模式来重新进行封装。为避免每个请求的域名+端口
字段没必要的重复以及满足项目上线后方便域名的切换功能,在api文件夹下的config.js文件中用axios.create配置指定基础路径baseUrl且axios.create返回一个新创建的axios实例。代码如下:
import axios from 'axios'
export const baseUrl = "https://www.fastmock.site/mock/d561e8a3e79db6ef8c66099546e04efa/qidian"
const axiosInstance = axios.create({
baseURL: baseUrl
})
export { axiosInstance }
在config.js文件配置基础路径后,我们再在api文件夹下的request.js文件中封装每条请求的具体路径。代码如下:
import { axiosInstance } from "./config";
export const getBannerRequest =
() => axiosInstance.get('./banners')//Banners模块
export const getNavbarsRequest =
() => axiosInstance.get('./navbars')
...
export const getHotRequest =
() => axiosInstance.get('./hotlist')
export const getPopularRequest =
() => axiosInstance.get('./popularlist')
1.2 CSSTransition 实现路由跳转动画
为给页面路由跳转添加某种过渡动画,以增加用户体验。效果如下:
我采用了React为我们提供`react-transition-group`的`CSSTransition`组件。 这个组件可以帮助我们方便的实现路由的 `入场` 和 `离场` 动画,使用时不要忘了安装 react-transition-group。代码如下:import { CSSTransition } from 'react-transition-group'
<CSSTransition
in={show}
timeout={30000}
appear={true}
classNames="fly"
unmountOnExit
onExit={() => {
navigate(-1)
}}
>
...
</CSSTransition>
CSSTransition用于通过不同CSS的指定形式来展现动画。
它有**-enter**(入场动画初始状态,第一帧),-enter-active(入场动画进行时),-enter-done(入场完成后的样式,一般就是-enter-active所指定的末状态,可以不指定),-exit(离场动画初始状态,第一帧),-exit-active(离场状态进行时),-exit-done(与enter-done类似,同样也几乎没有太大意义)这几个关键的CSS类别。
总结就是:
- 它们有三种状态,需要定义对应的CSS样式:
- 第一类,开始状态:对应的类是-enter
- 第二类:执行动画:对应的类是-active
- 第三类:执行结束:对应的类是done
CSS代码如下:
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background:#fbf1f2;
bottom: ${props => props.play > 0 ? '3rem' : 0};
width: 100%;
z-index: 100;
overflow: hidden;
transform-origin: right bottom;
/* CSSTranstion 过度类型给children */
&.fly-enter,&.fly-appear {
opacity: 0;
/* 启用GPU加速 */
transform: translate3d(100%, 0, 0);
}
&.fly-enter-active, &.fly-apply-active {
opacity: 1;
transition: all .3s;
transform: translate3d(0, 0, 0);
}
&.fly-exit {
opacity: 1;
transform: translate3d(0,0,0)
}
&.fly-exit-active {
opacity: 0;
transition: all .3s;
transform: translate3d(100%, 0, 0);
}
1.3 图片延迟加载
图片延迟加载就是涂片按需加载的意思。效果如下:
图片的延迟加载最常见的是只加载网页显示在屏幕中的图片,如果用户没有滚动到网页靠下的部分,那图片就不用显示了,节省HTTP请求和带宽,同时提高首屏的加载速度,提高用户体验。来自react-lazyload
的<Lazyload/>
组件为数据提供了一个默认图片,还未到达视口的图片由默认图片替代,等图片滚动到视口时再进行加载,优化了用户体验感。
import LazyLoad from 'react-lazyload'
<LazyLoad className='lazy'
placeholder={<img width="100%"
height="100%" src={book} />}>
<img
width="100%"
height="100%"
src={item.img + "?param=300x200"} alt="" />
</LazyLoad>
1.4 memo 组件性能优化
主要用于子组件当中当我们的父组件数据复杂,多项改变状态的地方,当父组件的改变,没有影响到子组件(props未变,没有props), 组件外面都加memo后就会使用上一次加载的数据,不会进行更新。以此通过记忆组件渲染的方式来提升性能的表现 也就是说使用React.memo之后可以避免不必要组件跟随页面更新而重新进行渲染。代码如下:
export default connect(mapStateToProps, mapDispatchToProps)(React.memo(Search))
或者
import { memo } from 'react'
export default connect(mapStateToProps, mapDispatchToProps)(memo(Search))
1.5 better-scroll 提升滑动体验
一般必须better-scroll 生成scroll 组件,这个组件我没有自己封装,而是使用了神三元封装的Scroll组件(要学习的同学可以在这src/components/common文件下拿哟),让移动端加载更多的标配提升滚动体验使得页面下拉刷新, 上拉加载更多。
<Scroll onScroll={forceCheck}>
...
</Scroll>
二、Redux的数据管理(正主登场)
前段时间学习大佬的开源项目在大佬的组件上几乎看不见useState
的身影,而是使用了Redux
来对数据进行管理,然后我就学着大佬的写法来重写我这个项目期间有什么不足的地方希望看官们敬请指正,我也会小心求证,力保知识的正确。
1.1 为什么用Redux
在React中,组件之间互相通信有四种情况:父组件向子组件通信、子组件向父组件通信、跨级组件通信和没有嵌套关系组件之间的通信。
其中最容易实现的是前面两种,因为在React中数据的流动是单向的,所以父组件向子组件通信也是最常见的,只需要通过props
向子组件传递需要的信息就好了。而子组件向父组件通信可以通过回调函数
和自定义的事件
进行通信,稍微麻烦,但也很简单。
而后面两种就难实现了,因为他们组件之间关系过弱,想要进行通信只能通过层层传递props来进行,如果是小型项目的话还能写,但当我们在写大型项目时,这无疑会产生许多可以避免的鸡肋代码为我们的coding带来麻烦,所以此时我们需要一个仓库来存放所有的状态,当我们需要使用某一条状态时只需要去对应的地方,取出对应的状态就好了。所以,此时就需要Redux站出来了。
1.2 Redux的原理
Redux的官方图:
React-redux的几个重要概念:
action
是把数据从应用(这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。
reducer
指定了应用状态的变化如何响应 actions并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
store
就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态。 在 Redux 中,所有的数据(比如state)被保存在一个store容器中 ,在一个应用程序中只能有一个store对象。当一个store接收到一个action,它将把这个action代理给相关的reducer。reducer是一个纯函数,它可以查看之前的状态,执行一个action并且返回一个新的状态。
Provider
其实就只是一个外层容器,它的作用就是通过配合 connect 来达到跨层级传递数据。使用时只需将Provider定义为整个项目最外层的组件,并设置好store。那么整个项目都可以直接获取这个store。它的原理其实是通过React中的Context来实现的。
具体的流程就是:
- 首先我们要确定我们要做什么
- Action Creators创建action(也就是你要做的事情)
- 然后通过dispatch将action分发出去
- store对要使用的reducer进行绑定,然后将action分发到对应的reducer上
- 在reducer上进行相应的action操作并返回结果给store
- 组件就可以通过store的API向store进行获取操作返回的结果
1.3 本次项目的Redux的运用
如果小伙伴们还有些迷糊的话不要着急可以先看看下面具体的应用
创建store
import { createStore, compose, applyMiddleware } from 'redux'
// 组件 中间件redux-thunk 数据
import thunk from 'redux-thunk'//异步数据管理
import reducer from './reducer'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer,
// 组合函数
// devtool
composeEnhancers(
// 异步
applyMiddleware(thunk)
)
)
export default store;
首先我们通过redux的api createStore
来创建了一个仓库,然后引入了一个React-thunk
,这是一个比较流行的 redux 异步 action 中间件。比如 action 中有通过 axios
请求数据,那么就应该使用 redux-thunk 了。redux-thunk 帮助你统一了异步和同步 action 的调用方式,把异步过程放在 action 级别解决,对 component 没有影响。再就是ReduxDevTools
,这是Redux在浏览器调试state的插件(下载链接在这)。
创建action
import { getMoreRequest } from "@/api/request"
import * as actionTypes from './constants'
export const changeMoreList = (data) => ({
type: actionTypes.CHANGE_MORE,
data: data
})
export const getMoreList = () => {
return (dispatch) => {
getMoreRequest()
.then(data => {
console.log(data, "更多数据表");
dispatch(changeMoreList(data));
})
}
}
changeMoreList用于创建 action
对象
getMoreList用于请求数据, 并 dispatch
一个 action
对象, 即将 action
对象发送出去
注意一般常量统一放在actionCreators.js
文件,用于管理所有action
的type
,方便维护
export const CHANGE_MORE = 'CHANGE_MORE'
Reducer
import * as actionTypes from './constants'
const defaultState = {
moreList: []
}
export default (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_MORE:
return {
...state,
moreList: action.data
}
default:
return newState
}
}
Reducer用于将store发过来的action完成并将结果返回给store,他接收两个参数defaultState(旧状态)和action(动作)并返回一个newState(新状态)。
在组件的运用
function More(props) {
const navigate = useNavigate()
const { moreList } = props
const { getMoreDataDispatch } = props
const [show, setShow] = useState(false);
let { id } = useParams();
useEffect(() => {
setShow(true),
getMoreDataDispatch();
}, [])
return(
...
)
}
const mapStateToProps = (state) => {
return {
moreList: state.more.moreList
}
}
const mapDispatchToProps = (dispatch) => {
return {
getMoreDataDispatch() {
dispatch(actionCreators.getMoreList())
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(React.memo(More))
mapStateToProps: 我们传入参数 state
, 即全部数据, 再将我们所需要的数据从 state
取出来如:moreList, 把它封装成一个对象, 最后返回到 props
中
mapDispatchToProps: 我们传入 dispatch
函数, 它将会接收一个 action
对象, 并传出, 根据 action.type
的值来修改对应的 data
总结
在这一段时间中,虽学习了三元大佬的项目但新学习的技术使用还是有些青涩,无法熟练的使用所以文章中不可避免会出现错误,希望看官们在评论区不吝指点一起交流。但这个系列还没有完结,下一篇文章内容预告是和TS+Redux
相关,用TS重构项目再加一些redux数据交互。还是那句话希望各位看官能持续追更,陪笔者一起成长! 源代码在这