未实现css样式,仅供学习原理使用。
下拉刷新功能核心功能点
1、下拉的时候是否处于顶部(scrollTop),如果不是在顶部下拉,则不会触发刷新事件
2、开始下拉时(touchstart)需要记录y的值
3、移动时(touchmove)判断y的偏移量是否大于最大偏移量maxY,是则设置当前状态为AWAIT(释放立即刷新),否则设置为START(开始刷新)
4、下拉结束时(touchend)判断状态是否为AWAIT,是则刷新,否则直接设置结束状态
下拉刷新方法和常量:
import { useEffect, useRef, useState } from 'react';
// 最大偏移量
const MAX_Y = 100;
// 状态
export const STATUS = {
START: 'start', // 开始下拉刷新
AWAIT: 'await', // 释放立即刷新
LOADING: 'loading', // 正在刷新
SUCCESS: 'success', // 刷新成功
FINISH: 'finish', // 刷新完成
};
// 下拉刷新时 页面上展示的文字
export const TIPS = {
[STATUS.START]: '开始下拉刷新',
[STATUS.AWAIT]: '释放立即刷新',
[STATUS.LOADING]: '正在刷新',
[STATUS.SUCCESS]: '刷新成功',
};
export const usePullToRefresh = (onRefresh: () => void) => {
const containerRef = useRef<HTMLDivElement>(null);
const [status, setStatus] = useState(STATUS.FINISH);
const y = useRef(0);
useEffect(() => {
if (!containerRef.current) return;
containerRef.current.ontouchstart = (e) => {
// 阻止浏览器默认行为(下拉顶部出现移动)
e.preventDefault();
// 判断下拉的时候是否处于顶部
if (document.documentElement.scrollTop === 0) {
// 记录y的值
y.current = e.touches[0].pageY;
}
};
containerRef.current.ontouchmove = (e) => {
e.preventDefault();
if (document.documentElement.scrollTop === 0) {
// 如果移动的距离大于最大值,则设置当前状态为释放立即刷新,并直接返回
if (e.touches[0].pageY - y.current > MAX_Y) {
setStatus(STATUS.AWAIT);
return;
}
// 如果移动的距离小于最大值,则设置当前状态为开始下拉刷新
if (e.touches[0].pageY - y.current > 0) {
setStatus(STATUS.START);
}
}
};
return () => {
if (!containerRef.current) return;
// 移除事件监听
containerRef.current.ontouchstart = null;
containerRef.current.ontouchmove = null;
};
}, []);
useEffect(() => {
if (!containerRef.current) return;
containerRef.current.ontouchend = async (e) => {
e.preventDefault();
// 判断当前状态为释放立即刷新时,才执行刷新操作
if (status === STATUS.AWAIT) {
setStatus(STATUS.LOADING);
await onRefresh();
setStatus(STATUS.SUCCESS);
setTimeout(() => {
setStatus(STATUS.FINISH);
}, 500);
return;
}
setStatus(STATUS.FINISH);
};
return () => {
if (!containerRef.current) return;
containerRef.current.ontouchend = null;
};
}, [status]);
return { status, containerRef };
};
PullToRefresh组件:
import React, { memo } from 'react';
import { STATUS, TIPS, usePullToRefresh } from './hooks';
import { AutoCenter } from 'antd-mobile';
interface IProps {
children: React.ReactNode;
onRefresh: () => void;
}
const PullToRefresh = memo(({ children, onRefresh }: IProps) => {
const { status, containerRef } = usePullToRefresh(onRefresh);
return (
<div ref={containerRef}>
{status !== STATUS.FINISH && <AutoCenter>{TIPS[status]}</AutoCenter>}
{children}
</div>
);
});
export default PullToRefresh;
使用时将PullToRefresh组件包裹需要刷新的组件即可。
<PullToRefresh onRefresh={() => console.log('refresh')}>
<Component />
</PullToRefresh>