本节代码对应 GitHub 分支: chapter3
# 图片懒加载
在大量图片加载的情况下,会造成页面空白甚至卡顿,然而我们的视口就这么大,因此只需要让视口内的图片显示即可,同时图片未显示的时候给它一个默认的 src,让一张非常精简的图片占位。这就是图片懒加载的原理。当然,在本项目中,我们采取一个成熟的方案 react-lazyload 库,易上手,效果不错。
npm install react-lazyload --save
//components/list.js
// 引入
import LazyLoad from "react-lazyload";
//img 标签外部包裹一层 LazyLoad
<LazyLoad placeholder={<img width="100%" height="100%" src={require ('./music.png')} alt="music"/>}>
<img src={item.picUrl + "?param=300x300"} width="100%" height="100%" alt="music"/>
</LazyLoad>
至于默认的占位图片,大家可以去相应分支去拿。
现在我们做到了视口内的图片显示真实资源,视口外则显示占位图片,那么当我们滑动的时候,如何让下面相应的图片显示呢?
其实也相当简单,在 Recommend/index.js 中:
// 引入 forceCheck 方法
import { forceCheck } from 'react-lazyload';
//scroll 组件中应用这个方法
<Scroll className="list" onScroll={forceCheck}>
...
这样随着页面滑动,下面的图片会依次显示,没有任何问题。
# 进场 loading 效果
Ajax 请求往往需要一定的时间,在这个时间内,页面会处于没有数据的状态,也就是空白状态,但是用户点击来的时候看见一片空白的时候心里是非常焦灼的,尤其是 Ajax 的请求时间长达几秒的时候,而 loading 效果便能减缓这种焦急的情绪,并且如果 loading 动画做的漂亮,还能够让人赏心悦目,让用户对 App 产生好感。
loading 的重要性不言而喻。因此,我也是这花费了不少力气,折腾出了几个版本的 loading 效果。这里先来写第一版。
主要是利用了 CSS3 的 animation-delay 特性,让两个圆交错变化,产生一个涟漪的效果。
import React from 'react';
import styled, { keyframes } from'styled-components';
import style from '../../assets/global-style';
const loading = keyframes`
0%, 100% {
transform: scale(0.0);
}
50% {
transform: scale(1.0);
}
`
const LoadingWrapper = styled.div`
>div {
position: fixed;
z-index: 1000;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 60px;
height: 60px;
opacity: 0.6;
border-radius: 50%;
background-color: ${style ["theme-color"]};
animation: ${loading} 1.4s infinite ease-in;
}
>div:nth-child (2) {
animation-delay: -0.7s;
}
`
function Loading () {
return (
<LoadingWrapper>
<div></div>
<div></div>
</LoadingWrapper>
);
}
export default React.memo (Loading);
现在在 Recommend 组件中引入
import Loading from '../../baseUI/loading/index';
// 在返回的 JSX 代码中
<Content>
...
<Loading></Loading>
<Content>
现在你可以看到屏幕中间的 loading。接下来添加 Loading 的控制逻辑。
由于数据是异步获取,异步逻辑全在 redux-thunk 中执行,且 loading 和数据之间是一个联动的关系,因此 loading 的状态应放在 redux 管理。
- 首先,在 Recommend/store 下的 reducer.js 中:
//reducer.js
const defaultState = fromJS ({
...
enterLoading: true
});
- 添加 action 的 type 值常量
//constants.js
...
export const CHANGE_ENTER_LOADING = 'recommend/CHANGE_ENTER_LOADING';
- 添加 reducer 的逻辑:
export default (state = defaultState, action) => {
switch (action.type) {
...
case actionTypes.CHANGE_ENTER_LOADING:
return state.set ('enterLoading', action.data);
default:
return state;
}
}
- 然后编写 action:
//actionCreators.js
...
export const changeEnterLoading = (data) => ({
type: actionTypes.CHANGE_ENTER_LOADING,
data
});
// 另外在获取推荐歌单后,应把 loading 状态改为 false
export const getRecommendList = () => {
return (dispatch) => {
getRecommendListRequest ().then (data => {
dispatch (changeRecommendList (data.result));
dispatch (changeEnterLoading (false));// 改变 loading
}).catch (() => {
console.log ("推荐歌单数据传输错误");
});
}
};
接下来在组件中应用这个 enterLoading:
//recommend/index.js
const mapStateToProps = (state) => ({
...
enterLoading: state.getIn (['recommend', 'enterLoading'])
});
// 返回的 JSX 代码中应用它
<Content>
...
{ enterLoading ? <Loading></Loading> : null }
<Content>
这样 Loading 效果就正常显示啦!
# Redux 数据缓存
问题:其实还有一个细节需要我们来优化,就是你现在切换到歌手页面,然后切回到推荐页,你在浏览器的 Network 中会看到又发了两次网络请求,而这两次请求是完全没有必要的,纯属浪费性能。

那如何来优化呢?根据我们这个项目的特点,利用 Redux 的数据来进行页面缓存成本最低,是不二之选。
其实操作起来也是非常简单的,只需要做一些小小的改动:
//Recommend/index.js
useEffect (() => {
// 如果页面有数据,则不发请求
//immutable 数据结构中长度属性 size
if (!bannerList.size){
getBannerDataDispatch ();
}
if (!recommendList.size){
getRecommendListDataDispatch ();
}
}, []);
这下,我切换到歌手页,再切回来,果然就不会多发请求啦!
恭喜你,现在已经完成了推荐模块的内容,是不是相当有成就感呢?后面还有更多有挑战的事情等着你呢,加油!