体验小程序 react hook——实现列表加载

1,152 阅读4分钟

基于Taro框架

函数页面配置

function TestPage() {
}
TestPage.config = {
//在这里配置小程序页面相关内容
}
export default TestPage

函数组件配置

function TestCom(props) {
}
TestCom.options = {
    addGlobalClass: true
}
export default TestCom

数据加载

const [list, setList] = useState([])
useEffect( async () => {
    let data = await getData()
    setList(data)
}, [])//传空数组模拟componentDidMount 功能

写一个列表加载组件

功能分析

  • 空状态
  • 下拉刷新
  • 上拉加载
  • 加载完成提示
  • 加载截流

实现方案

  • 使用小程序的下拉刷新和上拉加载功能
  • 抽象空状态组件
  • 自定义effect抽象加载方案

列表组件代码 1.0

import {View} from "@tarojs/components";
import Empty from "../empty";

function List(props) {
    const { listTotal, listLength, emptyTip, children } = props
    let isEmpty = listLength === 0
    let isLast = listLength >= listTotal
    return (
        <View>
            {
                children//列表内容
            }
            {
                isEmpty//列表为空时显示
                &&
                <Empty title={emptyTip} />
            }
            
            {
                !isEmpty && isLast//非空状态加载完毕显示
                &&
                <View className='paddingY30 color-5 font-size-4 text-align-center'>我也是有底线的</View>
            }
        </View>
    )
}

List.options = {
    addGlobalClass: true
}

export default List

useGetList 1.0 实现下拉加载


import Taro, {useEffect, useReachBottom, useState} from "@tarojs/taro";

export default function useGetList(getList) {//接收数据加载方法为参数
    if (!getList) return []
    const [list, setList] = useState([]);//列表数据
    const [page, setPage] = useState(1);//列表页
    useEffect(async ()=>{
        try {
            let {list: listGet} = await getList(page)
            let newList =  page === 1 ? listGet : [...list, ...listGet]
            setList(newList)//更新列表
        } catch (e) {
            console.log(e)
        }
    }, [page])//页数改变的时候执行

    useReachBottom(()=> {
        let pageMore = page + 1
        setPage(pageMore)
    })
    return list
}

useGetList 1.1 实现加载中截流和加载完毕截流

默认后端返回列表总数,没有就让后端


import Taro, {useEffect, useReachBottom, useState} from "@tarojs/taro";

export default function useGetList(getList) {
    if (!getList) return []
    const [list, setList] = useState([]);
    const [page, setPage] = useState(1);
    const [count, setCount] = useState(null); //列表总数
    let loading = false //判断是否正在加载
    useEffect(async ()=>{
        let isLast = typeof count === 'number' && list.length >= count //判断是否已经加载完毕
        let isStop = loading || isLast //是否截流:正在加载或者下拉加载已经完毕
        if (!isStop) {
            loading = true
            try {
                let {list: listGet, count: countGet} = await getList(page) //默认后端返回列表总数
                let newList =  page === 1 ? listGet : [...list, ...listGet]
                setList(newList)
                countGet !== count && setCount(countGet) //更新列表总数
            } catch (e) {
                console.log(e)
            }
            setTimeout(()=> {
                loading = false //重置加载状态
            }, 0)
        }
    }, [page])//页数改变的时候执行

    useReachBottom(()=> {
        let pageMore = page + 1
        setPage(pageMore)
    })
    return list
}

useGetList 2.0 实现下拉刷新

考虑到page=1时刷新需求

同时下拉刷新与上来加载属于不同加载类型

所以增加参数——加载类型type,作为执行useEffect回调条件

import Taro, {useEffect, usePullDownRefresh, useReachBottom, useState} from "@tarojs/taro";

export default function useGetList(getList) {
    if (!getList) return []
    const [page, setPage] = useState(1);
    const [list, setList] = useState([]);
    const [count, setCount] = useState(null);
    const [type, setType] = useState('load');//加载类型,'load','refresh'等
    let loading = false
    useEffect(async ()=>{
        let isLast = typeof count === 'number' && list.length >= count
        let isLoadMore = type === 'load'//判断是否为下拉加载
        let isStop = loading || (isLoadMore && isLast)//正在加载或者下拉加载已经完毕——截流
        if (!isStop) {
            loading = true
            try {
                let {list: listGet, count: countGet} = await getList(page)
                let newList =  page === 1 ? listGet : [...list, ...listGet]
                setList(newList)
                countGet !== count && setCount(countGet)
            } catch (e) {
                console.log(e)
            }
            setTimeout(()=> {
                type === 'refresh' && Taro.stopPullDownRefresh() //下拉刷新停止
                loading = false
            }, 0)
        }

    }, [page, type]) //页数改变或者加载类型改变的时候执行

    useReachBottom(()=> {
        let pageMore = page + 1
        setPage(pageMore)
        type !== 'load' && setType('load') //重置加载状态
    })

    usePullDownRefresh(()=>{ //注意页面需要配置允许刷新
        setType('refresh') //更改加载状态
        page !== 1 && setPage(1)
    })
    return [list, count]
}
Index.config = {
    navigationBarTitleText: '测试页面',
    enablePullDownRefresh: true //页面配置下拉刷新
}

上拉加载以更新page值来重新执行useEffect的回调

下拉刷新以更新type值来重新执行useEffect的回调

到这里小程序的列表加载组件已经可以用了


使用例子

import { View } from "@tarojs/components";
import DeviceCard from "../../components/device/deviceCard";
import List from "../../components/list";
import useGetList from "../../effects/useGetList";

//这个函数可以提取到后端请求文件里
async function getDevices(page) {
    let list =  [
        {
            image: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3744924158,1824985192&fm=26&gp=0.jpg',
            title: '1如何轻松在Coinicgame',
            content: '维马逊自动封口机商用全自动连续热封机来自雅马哈',
            id: 1
        },
        {
            image: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3744924158,1824985192&fm=26&gp=0.jpg',
            title: '2如何轻松在Coinicgame',
            content: '维马逊自动封口机商用全自动连续热封机来自雅马哈',
            id: 2
        }
    ]
    return {list, count: 24}
}

function Index() {
    const [list , count] = useGetList(getDevices, null)
    return (
        <View className='padding-bottom20'>
            <List length={list ? list.length : null}
                  count={count}>
                <View className='marginX20 flex-wrap'>
                    {
                        list && list.length && list.map((device,index) => (
                            <DeviceCard key={device.name + index}
                                        device={device} />
                        ))
                    }
                </View>
            </List>
        </View>
    )
}

Index.config = {
    navigationBarTitleText: '测试设备列表',
    enablePullDownRefresh: true
}

export default Index

接下去做的事就是让它更好用

优化目标

  • 列表组件支持接收列表子组件作为参数

折腾了半天没成功,函数组件新手

  • 扩展列表数据筛选功能——搜索、筛选

useGetList 3.0 增加搜索/筛序功能

import Taro, {useEffect, usePullDownRefresh, useReachBottom, useState} from "@tarojs/taro";
//暴露筛序更新方法
function createUseFilter(setPage, setType, setFilter) {
    return (filter) => {
        setPage(1)
        setType('search')
        setFilter(filter)
    }
}
export default function useGetList(getList) {
    if (!getList) return []
    const [list, setList] = useState([]);
    const [count, setCount] = useState(null);
    const [page, setPage] = useState(1);
    const [type, setType] = useState('load');
    const [filter, setFilter] = useState({}); //增加筛序条件
    let loading = false
    useEffect(async ()=>{
        let isLast = typeof count === 'number' && list.length >= count
        let isLoadMore = type === 'load'
        let isStop = loading || (isLoadMore && isLast)
        if (!isStop) {
            loading = true
            try {
                let {list: listGet, count: countGet} = await getList(page, filter) //传入筛选条件
                let newList =  page === 1 ? listGet : [...list, ...listGet]
                setList(newList)
                countGet !== count && setCount(countGet)
            } catch (e) {
                console.log(e)
            }
            type === 'refresh' && Taro.stopPullDownRefresh()
            setTimeout(()=> {
                loading = false
            }, 0)
        }

    }, [page, type, filter])
    useReachBottom(()=> {
        let pageMore = page + 1
        setPage(pageMore)
        setType('load')
    })
    usePullDownRefresh(()=>{
        setPage(1)
        setType('refresh')
    })
    return [list, count, filter, createUseFilter(setPage, setType, setFilter)] //暴露筛选更新方法和目前筛选条件
}

使用

let searchInv = -1

function Index() {
    const [list , count, filter, useActionFilter] = useGetList(getDevices, {search: ''})
    return (
        <View className='news-manage padding-bottom20'>
            <View>
                <Input type='text'
                       placeholder='请输入关键词'
                       onInput={(e) => {
                           clearTimeout(searchInv)
                           searchInv = setTimeout(()=> {
                               console.log('e',e.detail.value)
                               useActionFilter({search: e.detail.value})
                           }, 300)
                       }}
                />
            </View>
           <List length={list ? list.length : null}
                 count={count}>
               <View className='marginX20 flex-wrap'>
                   {
                       list && list.length && list.map((device,index) => (
                           <DeviceCard key={device.name + index}
                                       device={device} />
                       ))
                   }
               </View>
           </List>
        </View>
    )
}

特别提醒:Taro中一个文件只能定义一个组件


第一次体验hook有什么问题轻喷,请斧正。