本节代码对应 GitHub 分支: chapter5
# 数据层开发
# axios 请求代码
在 api/request.js 中,添加以下代码:
export const getRankListRequest = () => {
return axiosInstance.get (`/toplist/detail`);
};
# redux 层开发
排行榜单可以说是整个应用中就数据层而言最简单的一个组件。因此 redux 的代码我们集中在一个文件中。
//rank/store/index.js
import { fromJS } from 'immutable';
import { getRankListRequest } from '../../../api/request';
//constants
export const CHANGE_RANK_LIST = 'home/rank/CHANGE_RANK_LIST';
export const CHANGE_LOADING = 'home/rank/CHANGE_LOADING';
//actionCrreator
const changeRankList = (data) => ({
type: CHANGE_RANK_LIST,
data: fromJS (data)
})
export const getRankList = () => {
return dispatch => {
getRankListRequest ().then (data => {
let list = data && data.list;
dispatch (changeRankList (list));
dispatch (changeLoading (false));
})
}
}
const changeLoading = (data) => ({
type: CHANGE_LOADING,
data
})
//reducer
const defaultState = fromJS ({
rankList: [],
loading: true
})
const reducer = (state = defaultState, action) => {
switch (action.type) {
case CHANGE_RANK_LIST:
return state.set ('rankList', action.data);
case CHANGE_LOADING:
return state.set ('loading', action.data);
default:
return state;
}
}
export { reducer };
# 组件连接 redux
先在全局 store 注册:
//src/store/reducer.js
import { combineReducers } from 'redux-immutable';
import { reducer as recommendReducer } from '../application/Recommend/store/index';
import { reducer as singersReducer } from '../application/Singers/store/index';
import { reducer as rankReducer } from '../application/Rank/store/index';
export default combineReducers ({
// 之后开发具体功能模块的时候添加 reducer
recommend: recommendReducer,
singers: singersReducer ,
rank: rankReducer
});
然后让 rank 组件连接 redux,
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
function Rank (props) {
}
// 映射 Redux 全局的 state 到组件的 props 上
const mapStateToProps = (state) => ({
rankList: state.getIn (['rank', 'rankList']),
loading: state.getIn (['rank', 'loading']),
});
// 映射 dispatch 到 props 上
const mapDispatchToProps = (dispatch) => {
return {
getRankListDataDispatch () {
dispatch (getRankList ());
}
}
};
export default connect (mapStateToProps, mapDispatchToProps)(React.memo (Rank));
已经熟悉了 redux 开发方式的你,是不是也觉得非常简单呢?废话不多说,我们马上进入 Rank 组件的开发。
# Rank 组件开发
首先初始化相应的 props。
(这里的引入模块的代码大家自行参考 GitHub 仓库的 chapter5 分支,也根据命令行报错提示依次引入)
const { rankList:list, loading } = props;
const { getRankListDataDispatch } = props;
let rankList = list ? list.toJS () : [];
didMount 的时候发送 Ajax 请求:
useEffect (() => {
getRankListDataDispatch ();
}, []);
排行榜单分为两个部分,一部分是官方榜,另一部分是全球榜。
官方榜单数据有 tracks 数组,存放部分歌曲信息,而全球榜没有。
但是后端数据并没有将这两者分开,因此我们需要做一下数据的处理。
let globalStartIndex = filterIndex (rankList);
let officialList = rankList.slice (0, globalStartIndex);
let globalList = rankList.slice (globalStartIndex);
其中,filterIndex 从 api/utils.js 中导出,
// 处理数据,找出第一个没有歌名的排行榜的索引
export const filterIndex = rankList => {
for (let i = 0; i < rankList.length - 1; i++) {
if (rankList [i].tracks.length && !rankList [i + 1].tracks.length) {
return i + 1;
}
}
};
// 记得引入这个方法
import { filterIndex } from '../../api/utils';
// 这是渲染榜单列表函数,传入 global 变量来区分不同的布局方式
const renderRankList = (list, global) => {
return (
<List globalRank={global}>
{
list.map ((item) => {
return (
<ListItem key={item.coverImgId} tracks={item.tracks} onClick={() => enterDetail (item.name)}>
<div className="img_wrapper">
<img src={item.coverImgUrl} alt=""/>
<div className="decorate"></div>
<span className="update_frequecy">{item.updateFrequency}</span>
</div>
{ renderSongList (item.tracks) }
</ListItem>
)
})
}
</List>
)
}
const renderSongList = (list) => {
return list.length ? (
<SongList>
{
list.map ((item, index) => {
return <li key={index}>{index+1}. {item.first} - {item.second}</li>
})
}
</SongList>
) : null;
}
// 榜单数据未加载出来之前都给隐藏
let displayStyle = loading ? {"display":"none"}: {"display": ""};
return (
<Container>
<Scroll>
<div>
<h1 className="offical" style={displayStyle}> 官方榜 </h1>
{ renderRankList (officialList) }
<h1 className="global" style={displayStyle}> 全球榜 </h1>
{ renderRankList (globalList, true) }
{ loading ? <EnterLoading><Loading></Loading></EnterLoading> : null }
</div>
</Scroll>
{renderRoutes (props.route.routes)}
</Container>
);
style.js 中:
import styled from'styled-components';
import style from '../../assets/global-style';
// Props 中的 globalRank 和 tracks.length 均代表是否为全球榜
export const Container = styled.div`
position: fixed;
top: 90px;
bottom: 0;
width: 100%;
.offical,.global {
margin: 10px 5px;
padding-top: 15px;
font-weight: 700;
font-size: ${style ["font-size-m"]};
color: ${style ["font-color-desc"]};
}
`;
export const List = styled.ul`
margin-top: 10px;
padding: 0 5px;
display: ${props => props.globalRank ? "flex": "" };
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
background: ${style ["background-color"]};
&::after {
content:"";
display:block;
width: 32vw;
}
`
export const ListItem = styled.li`
display: ${props => props.tracks.length ? "flex": ""};
padding: 3px 0;
border-bottom: 1px solid ${style ["border-color"]};
.img_wrapper {
width: ${props => props.tracks.length ? "27vw": "32vw"};
height: ${props => props.tracks.length ? "27vw": "32vw"};
border-radius: 3px;
position: relative;
.decorate {
position: absolute;
bottom: 0;
width: 100%;
height: 35px;
border-radius: 3px;
background: linear-gradient (hsla (0,0%,100%,0),hsla (0,0%,43%,.4));
}
img {
width: 100%;
height: 100%;
border-radius: 3px;
}
.update_frequecy {
position: absolute;
left: 7px;
bottom: 7px;
font-size: ${style ["font-size-ss"]};
color: ${style ["font-color-light"]};
}
}
`;
export const SongList = styled.ul`
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 10px 10px;
>li {
font-size: ${style ["font-size-s"]};
color: grey;
}
`;
然后在 index.js 中引入 CSS 组件即可,代码就不展示了。
在 image_wrapper 中,我们再次利用渐变效果实现了一层遮罩,达到衬托文字的效果。
其实布局都是非常常用的 flex 布局,我就不在这上面浪费时间了。值得注意的是,当 flex 布局一行填满三个元素,但是最后一行只有两个元素的时候,会出现一些问题,你会发现最后一个元素并不是在居中的位置,而是在最右边,中间留出了空白。我当时就遇到了这个问题,最后采用伪元素的方式才得以解决:
export const List = styled.ul`
margin-top: 10px;
padding: 0 5px;
display: ${props => props.globalRank ? "flex": "" };
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
background: ${style ["background-color"]};
&::after {
content:"";
display:block;
width: 32vw;
}
`
现在的接口列表数据比之前少了一条,因此不再存在这个问题,但是希望大家能了解到这个细节。
阅读全文