8000 Ant Design Mobile ListView 长列表 逻辑封装 · Issue #34 · xianzou/blog · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
Ant Design Mobile ListView 长列表 逻辑封装 #34
Open
@xianzou

Description

@xianzou

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

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0