本节代码对应 GitHub 分支: chapter6
# axios 请求准备
在 api/request.js 中加入:
export const getAlbumDetailRequest = id => {
return axiosInstance.get (`/playlist/detail?id=${id}`);
};
# redux 层开发
# 1. 声明初始化 state
//reducer.js
import * as actionTypes from './constants';
import { fromJS } from 'immutable';
const defaultState = fromJS ({
currentAlbum: {},
enterLoading: false,
})
# 2. 定义 constants
//constants.js
export const CHANGE_CURRENT_ALBUM = 'album/CHANGE_CURRENT_ALBUM';
export const CHANGE_ENTER_LOADING = 'album/CHANGE_ENTER_LOADING';
# 3. 定义 reducer 函数
//reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case actionTypes.CHANGE_CURRENT_ALBUM:
return state.set ('currentAlbum', action.data);
case actionTypes.CHANGE_ENTER_LOADING:
return state.set ('enterLoading', action.data);
default:
return state;
}
};
# 4. 编写具体的 action
//actionCreators.js
import { CHANGE_CURRENT_ALBUM, CHANGE_ENTER_LOADING } from './constants';
import { getAlbumDetailRequest } from '../../../api/request';
import { fromJS } from 'immutable';
const changeCurrentAlbum = (data) => ({
type: CHANGE_CURRENT_ALBUM,
data: fromJS (data)
});
export const changeEnterLoading = (data) => ({
type: CHANGE_ENTER_LOADING,
data
});
export const getAlbumList = (id) => {
return dispatch => {
getAlbumDetailRequest (id).then (res => {
let data = res.playlist;
dispatch (changeCurrentAlbum (data));
dispatch (changeEnterLoading (false));
}).catch (() => {
console.log ("获取 album 数据失败!")
});
}
};
# 5. 将相关变量导出
//index.js
import reducer from './reducer'
import * as actionCreators from './actionCreators'
export { reducer, actionCreators };
# 组件连接 Redux
首先,需要将 Album 下的 reducer 注册到全局 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';
import { reducer as albumReducer } from '../application/Album/store/index';
export default combineReducers ({
recommend: recommendReducer,
singers: singersReducer ,
rank: rankReducer,
album: albumReducer
});
现在在 Album/index.js 中,准备连接 Redux。 增加代码:
import { connect } from 'react-redux';
// 组件代码
// 映射 Redux 全局的 state 到组件的 props 上
const mapStateToProps = (state) => ({
currentAlbum: state.getIn (['album', 'currentAlbum']),
enterLoading: state.getIn (['album', 'enterLoading']),
});
// 映射 dispatch 到 props 上
const mapDispatchToProps = (dispatch) => {
return {
getAlbumDataDispatch (id) {
dispatch (changeEnterLoading (true));
dispatch (getAlbumList (id));
},
}
};
// 将 ui 组件包装成容器组件
export default connect (mapStateToProps, mapDispatchToProps)(React.memo (Album));
# 组件对接真实数据
在组件代码中,
import React, {useState, useCallback, useRef, useEffect} from 'react';
import { getAlbumList, changeEnterLoading } from './store/actionCreators';
// 从路由中拿到歌单的 id
const id = props.match.params.id;
const { currentAlbum:currentAlbumImmutable, enterLoading } = props;
const { getAlbumDataDispatch } = props;
useEffect (() => {
getAlbumDataDispatch (id);
}, [getAlbumDataDispatch, id]);
// 同时将 mock 数据的代码删除
let currentAlbum = currentAlbumImmutable.toJS ();
但是如果你现在进入页面,会报一个错误: cannot read avatarUrl of undefined
这是为什么呢?
当页面进入 Ajax 请求还没有获取数据时,currentAlbum 的值为初始态 {}。直到数据异步加载完成,currentAlbum 才会改变,那么在这个过程中,通过 currentAlbum.creator 为 undefined,通过 current.creator.avatarUrl 取值自然会报错。这样的问题在日常开发中非常常见,那怎么避免这个问题?
我们需要在渲染前做一个非空对象的判断。
首先在 api/utils 中写一个工具函数:
// 判断一个对象是否为空
export const isEmptyObject = obj => !obj || Object.keys (obj).length === 0;
然后自行导入到 Album 组件中。
组件中修改如下:
{
!isEmptyObject (currentAlbum) ? (
<Scroll
onScroll={handleScroll}
bounceTop={false}
>
// 省略内部代码
</Scroll>
) : null
}
这样页面就能正常显示啦。
# 进场 Loaing 动画添加
import Loading from '../../baseUI/loading/index';
// 在 Container 样式组件中添加
{ enterLoading ? <Loading></Loading> : null}
到此为止,UI 和数据已经打通了,接下来我们来做一些优化。
阅读全文
← 歌单详情2 准备静态模板 代码封装及优化 →