React hooks 封装入门级 scroll 组件

1,669 阅读2分钟

入门级react版scroll组件开发

什么是scroll组件?

动画1.gif

一句话来说就是,能够在固定高度里进行滚动展示每个项目的组件

功能:滚动到底部加载更多、错误提示、没有更多、没有更多数据底部提示

技术栈

react + react hooks + fetch

开发

第一步先把基础组件写好

index.js

    function Index(props) {
        return (
            <div className={'box-container'}>
                <scroll>
                </scroll>
            </div>
        )
    }

scroll.js

function Scroll(props) {
        return (
            <ul className={'scroll-container'}>
            2131
            </ul>
        )
    }

item.js

function Item(props) {
    return (
        <div className={'item-container'}>
            // 这里是为了渲染item也就是子组件
            {
                props.children instanceof Array ? props.children.map(item => item) : props.children
            }
        </div>
    );
}

第二步开始渲染列表数据

index.js

const [list, setList] = useState([])

const [totalCount, setTotalCount] = useState(0)

const [listQuery, setListQuery] = useState({
    page: 1,
    rows: 10
})

// 依赖 listQuery 数据, 如果分页参数page改变,需要重新发送请求新的数据
useEffect(() => {
    fetchData(listQuery).then(res => {
        console.log('listQuery change')
        const {bookList, totalCount} = res.data
        const result = [...list, ...bookList]  // 合并之前的数据
        setList(result)
        setTotalCount(totalCount)
    })
}, [listQuery]);

// 类似vue的computed,当list和totalCount改变的时候拿到计算后的值
const hasMore = useMemo(() => {
    return list.length < totalCount
}, [list, totalCount])

return (
    <div className={'box-container'}>
        <Scroll 
            sourceData = {list}
            renderItem = {item => (
                <Item key = {item.key}>
                   /*
                   这里放置Item的内容,也可以将单个数据传到Item里进行渲染
                   <Item key = {item.key} item={item}></Item>
                   */
                   <span>{item.title}</span>
                </Item>
            )}
            hasMore = {hasMore}
        >
        </Scroll>
    </div>
)

scroll.js

const { sourceData, renderItem, hasMore } = props

return (
    <ul className={'scroll-container'}>
        {
            sourceData.map(renderItem)
        }
    </ul>
);

第三步开始判断是否滚动到底部

const handleScroll = () => {
    // 没有更多的时候 滚动不需要计算高度加载更多。
    if(!hasMore) {
        return
    }
    const sr = scrollRef.current // scroll 最外层元素
    
    const sh = sr.scrollHeight
    const ch = sr.clientHeight
    const st = sr.scrollTop
    // 
    if(sh - (st + 10) <= ch) {
        // 加载更多
    }
}

第四步加载更多

index.js

    const [loading, setLoading] = useState(false)
    useEffect(() => {
    +++    setLoading(true)
        fetchPageData(listQuery).then(res => {
            console.log('listQuery change')
            const {bookList, totalCount} = res.data
            const result = [...list, ...bookList]
            // 滚动加载延迟时间
            setTimeout(() => {
                setList(result)
                setTotalCount(totalCount)
    +++            setLoading(false)
            }, 2000)
        })
    }, [listQuery]);
    
    return (
        <div className={'box-container'}>
            <Scroll
                sourceData={list}
                renderItem={(item, index) => (
                    <Item key={item.title}>
                        <span>{index}{index}</span>
                    </Item>
                )}
                load={() => {
                    // 将load事件传给scroll组件触发。
                    let num = listQuery.page + 1
                    let result = {...listQuery}
                    result.page = num
                    setListQuery(result)
                }}
                loading={loading}
                hasMore={hasMore}
            />
        </div>
    );

scroll.js

const handleScroll = () => {
+++    if(loading) {
+++       return
+++    }
    if(!hasMore) {
        return
    }

    const sr = scrollRef.current
    const sh = sr.scrollHeight
    const ch = sr.clientHeight

    const st = sr.scrollTop
    if(sh - (st + 10) <= ch) {
+++        load()
    }
}

// 判断是否展示loading图标
const showLoading = useMemo(() => hasMore && loading, [hasMore, loading]);

return (
    <ul className={'scroll-container'} ref={scrollRef} onScroll={handleScroll}>
        {
            sourceData.map(renderItem)
        }
        // 可以自定义loading图标
        {
            showLoading && <div className={'loading'}>
                loading...
            </div>
        }
    </ul>
);

以上代码完成 滚动到底部加载更多、没有更多。

错误提示、没有更多数据底部提示功能在loading下面增加并加上判断即可,需要注意优先级。还可以在当加载超时时候增加重新加载按钮...

效果图

动画1.gif

谢谢观看

利用近期所学react知识做的一个小组件,希望有什么不足的地方大家可以指出.