Open
Description
Ant Design Mobile ListView 长列表 逻辑封装
采用hooks封装的 长列表 逻辑,支持上拉加载,下拉刷新,优化
rowHasChanged
逻辑,支持列表修改,并从新渲染对应行
代码:
// getListData请求方法
// idx唯一标识,用于控制列表单项刷新
export const useListView = (getListData, idx, linesPerPage = 10, cacheOptions = {}) => {
const dataRef = useRef([]);
const logNum = useRef(0);
const refreshRowId = useRef('');// 用来判断当前需要刷新的行
const isDownPullRefresh = useRef(false);
const [firstLoad, setFirstLoad] = useState(true);
const [pageInfo, setPageInfo] = useState({
currentPage: 1,
linesPerPage,
hasMore: true,
refreshing: true,
isLoading: true,
total: 0
});
const { cacheId, listViewDOM } = cacheOptions;
const [dataSource, setDataSource] = useState(() => {
return new ListView.DataSource({
rowHasChanged: (row1, row2) => {
// 第一次加载或者下拉刷新
if (isDownPullRefresh.current){
// 如果是最后一个的话就更新
if (dataRef.current.length === logNum.current){
isDownPullRefresh.current = false;
logNum.current = 0;
}
logNum.current += 1;
return true;
}
// 如果当前刷新的这一行等于需要刷新的行,返回true,表示需要更新该列
if (refreshRowId.current && (row1[idx] === refreshRowId.current || row2[idx] === refreshRowId.current)){
refreshRowId.current = '';
return true;
}
if (!row1 || !row2){
return true;
}
return row1[idx] !== row2[idx];
} // rowHasChanged(prevRowData, nextRowData); 用其进行数据变更的比较
});
});
const getData = async (ref = false) => {
const para = {};
para.linesPerPage = pageInfo.linesPerPage;
para.currentPage = pageInfo.currentPage;
// 第一次或者下拉刷新
if (ref){
para.currentPage = 1;
}
let data = {};
// 且如果设置了cacheId,从缓存中取
if (cacheId && pageInfo.currentPage === 1 && ref){
const readCache = sessionStorage.getItem('readCache');
const result = sessionStorage.getItem(cacheId);
if (result && JSON.parse(readCache)){
data = JSON.parse(result);
} else {
data = await getListData(para);
}
sessionStorage.removeItem(idx);
sessionStorage.removeItem(cacheId);
} else {
data = await getListData(para);
}
if (firstLoad){
setFirstLoad(false);
}
if (data && Array.isArray(data.list)){
const { list: dataList, total, pageNum } = data;
const len = dataList.length;
if (len <= 0) { // 判断是否已经没有数据了
setPageInfo({ ...pageInfo, refreshing: false, isLoading: false, hasMore: false });
return false;
}
if (ref){
// 这里表示刷新使用
// 下拉刷新的情况,重新添加数据即可(这里等于只直接用了第一页的数据)
// hasMore: true, // 下拉刷新后,重新允许开下拉加载
// refreshing: false, // 是否在刷新数据
setPageInfo({ ...pageInfo, hasMore: total > linesPerPage, refreshing: false, isLoading: false, currentPage: pageNum + 1, total });
// 保存数据进state,在下拉加载时需要使用已有数据
dataRef.current = [...dataList];
// 数据源中的数据本身是不可修改的,要更新datasource中的数据,请(每次都重新)调用cloneWithRows方法
setDataSource(dataSource.cloneWithRows(dataRef.current));
} else {
// 这里表示上拉加载更多
// 合并state中已有的数据和新增的数据
dataRef.current = [...dataRef.current, ...dataList];
setPageInfo({ ...pageInfo, refreshing: false, isLoading: false, hasMore: true, currentPage: pageNum + 1, total });
// 数据源中的数据本身是不可修改的,要更新datasource中的数据,请(每次都重新)调用cloneWithRows方法
setDataSource(dataSource.cloneWithRows(dataRef.current));
}
// 记录后台返回的信息,包含分页信息,注意更新list
if (cacheId){
const newData = { ...data, list: dataRef.current };
try {
sessionStorage.setItem(cacheId, JSON.stringify(newData));
sessionStorage.setItem('readCache', false);
} catch (error) {
console.log(error);
}
}
}
};
// 下拉刷新
const onRefresh = () => {
setPageInfo({ ...pageInfo, refreshing: true, isLoading: true });
setTimeout(() => {
isDownPullRefresh.current = true;
getData(true);
}, 100);
};
// 滑动到底部时加载更多
const onEndReached = () => {
// 加载中或没有数据了都不再加载
if (pageInfo.isLoading || !pageInfo.hasMore){
return false;
}
setPageInfo({ ...pageInfo, isLoading: true });
setTimeout(() => {
getData(false);
}, 100);
};
// 跳转新页面前开启缓存开关,用于打开缓存
const pushNewPageBeforeOpenCache = cacheParam => {
if (cacheId){
sessionStorage.setItem('readCache', true);
// eslint-disable-next-line
if (cacheParam || cacheParam == '0'){
sessionStorage.setItem(idx, cacheParam);
sessionStorage.setItem('scrollTopVal', listViewDOM.current.listviewRef.ListViewRef.ScrollViewRef.scrollTop);
}
} else {
sessionStorage.removeItem('readCache');
}
};
// 修改数据
const changeDataSource = async ({ current }, callback) => {
const { rowID } = current;
const currRow = await callback(dataRef.current[rowID]);
// 如果有返回值是删除里一行里面的内容
if (currRow){
let changeRowIndex = '';
// 替换修改数据
dataRef.current.forEach((row, index) => {
if (row[idx] === currRow[idx]){
changeRowIndex = index;
}
});
if (changeRowIndex || changeRowIndex === 0){
dataRef.current[changeRowIndex] = currRow;
}
refreshRowId.current = dataRef.current[rowID][idx];
setDataSource(dataSource.cloneWithRows(dataRef.current));
} else {
// 如果没有返回值,说明删除的是改行
// 允许刷新
isDownPullRefresh.current = true;
dataRef.current.splice(Number(rowID), 1);
setDataSource(dataSource.cloneWithRows(dataRef.current));
}
};
// 批量修改数据
const upMoredata = target => {
dataRef.current.forEach((item, index) => {
target.forEach(row => {
if (item[idx] === row[idx]){
for (const key in row){
// eslint-disable-next-line
if (dataRef.current[index].hasOwnProperty(key)){
dataRef.current[index][key] = row[key];
}
}
}
});
});
isDownPullRefresh.current = true;
setDataSource(dataSource.cloneWithRows(dataRef.current));
};
useEffect(() => {
// 回滚操作
const scrollTopVal = sessionStorage.getItem('scrollTopVal');
if (dataRef.current.length && cacheId && listViewDOM && scrollTopVal){
const { ScrollViewRef } = listViewDOM.current.listviewRef.ListViewRef;
if (ScrollViewRef){
ScrollViewRef.scrollTo(0, scrollTopVal);
}
sessionStorage.removeItem('scrollTopVal');
}
}, [dataRef.current]);//eslint-disable-line
useEffect(() => {
getData(true);
}, []); //eslint-disable-line
return {
dataSource,
pageInfo,
onRefresh,
onEndReached,
changeDataSource,
firstLoad,
pushNewPageBeforeOpenCache,
upMoredata
};
};
使用
import { useListView } from '@/utils/hooks';
function getDtList(body) {
return Network.formGet('/bjq/getDtList.do', body);
}
const List = () => {
const {
dataSource, pageInfo, onRefresh,
onEndReached, changeDataSource, firstLoad, upMoredata
} = useListView(getDtList, 'bjqDtId');
const renderRow = (rowData, sectionID, rowID) => {
...
}
const getNoData = (totalNum, length) => {
if (firstLoad){
return '';
}
if (totalNum === 0 || length === 0) {
return (
<div className="super-list-view-no-data">
<img className="super-list-view-empty-img" src={noDataImg} alt="" />
<p className="super-list-view-empty-description">暂无数据</p>
</div>
);
}
return '';
};
const listLength = dataSource.getRowCount();
return (
<ListView
dataSource={dataSource}
ref={r => (listViewDOM.current = r)}
renderFooter={
() => (
!pageInfo.hasMore ?
getNoData(pageInfo.total, listLength) :
pageInfo.isLoading ?
'加载中...' :
'上拉加载更多'
)
}
renderRow={renderRow}
style={{
height: document.documentElement.clientHeight - 44,
overflow: 'auto',
}}
pullToRefresh={
<PullToRefresh
refreshing={pageInfo.refreshing}
onRefresh={onRefresh}
/>}
onEndReachedThreshold={10}
onEndReached={onEndReached}
pageSize={5}
initialListSize={10}
/>
)
}
Metadata
Metadata
Assignees
Labels
No labels