手写H5简易下拉刷新功能

49 阅读2分钟

未实现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>