taro scroll-view组件正确下拉刷新姿势

version: 3.5.8

platform: h5、weapp

最近有个需求,需要给所有的列表页面加上下拉刷新的功能。记录下使用到的ScrollView组件踩到的坑和解决方案。

下拉刷新

taro框架如果需要在某个页面开启下拉刷新,首先需要在页面配置文件中添加配置:

ts 复制代码
// xxx/index.config.ts
export default {
  navigationBarTitleText: '页面标题',
  // 加上这一行
  enablePullDownRefresh: true,
};

添加该配置文件后需要重新启动taro。一般页面中,只需要再对下拉刷新的事件回调做出响应即可:

tsx 复制代码
//class 版本
class PageA extends Component{
    // 添加该生命周期响应下拉事件
    onPullDownRefresh(){
          //调用Taro.stopPullDownRefresh 停止下拉效果
        fetcher().then(()=>Taro.stopPullDownRefresh())
    }
}

// hooks版本
const PageB=()=>{
    usePullDownRefresh(()=>{
        //调用Taro.stopPullDownRefresh 停止下拉效果
        fetcher(params).then(()=>Taro.stopPullDownRefresh())
    });
}

当然如果需要主动触发下拉效果,则可以这么写:

tsx 复制代码
    const handlerPullDownRefresh=()=>{
        Taro.startPullDownRefresh();
        fetcher().then(()=>Taro.stopPullDownRefresh())
    }

只需要注意必须显示调用Taro.stopPullDownRefresh才能停止整个页面下的下拉效果。

如果页面上存在taro官方的ScrollView组件,那么默认情况下你会发现:

  1. ScrollView之外的部门可以正常下拉刷新。
  2. ScrollView手指下滑正常。
  3. ScrollView手指往上滑时,触发下拉刷新,不再滚动

这个问题无法参考taro官方提供的某个解决方案。究其原因,还是滚动穿透。之前没有过类似的需求,所以内部的组件库中滚动组件是这样修复这个问题的:

tsx 复制代码
class XXXScrollView extends Component<JdyScrollViewProps> {
  render() {
    // H5开启下拉刷新,scrollView的上滑手势失效
    return (
      <ScrollView {...this.props} onTouchMove={(e) => {
        e.stopPropagation();
        }}>
        {children}
      </ScrollView>
    );
  }
}

直接阻止滚动组件的滚动事件冒泡,改成这样的效果:

  1. 滚动区正常滚动。
  2. 滚动区无法触发页面下拉刷新。

由于滚动区一般会占用页面大部分空间,滚动区内无法触发该效果是无法忍受的,好在社区提供了一种解决方案

我们简单调整一下:

tsx 复制代码
interface Props extends ScrollViewProps {
}
const Index: FC<Props> = (props) => {
  const scrollTop = useRef(0);
  return (
    <ScrollView
      {...props}
      onTouchMove={(e) => {
        if (scrollTop.current !== 0) {
          e.stopPropagation();
        }
      }}
      onScroll={(e) => {
        scrollTop.current = e.detail.scrollTop;
      }}
    >
      {children}
    </ScrollView>
  );
};

现在可以做到:

  1. h5端一切正常。
  2. 微信小程序端scrollView部分无法触发下拉刷新,但是可以正常滑动。

微信小程序适配

首先讲下结论:小程序下使用原生scrollView是很难做到页面下拉刷新的。这里只适配scrollView的下拉刷新。

taro官方已经提供了小程序端的滚动区下拉刷新参数,只需要给scrollView组件设置:refresherEnabled、refresherTriggered、onRefresherRefresh即可开启scrollView的下拉刷新。遵循依赖注入的原则,我们这样拓展:

tsx 复制代码
const Index: FC<Props> = (props) => {
  const { children,  refreshFetcher = Promise.resolve } = props;
  const scrollTop = useRef(0);
  const [refreshStatus, setStatus] = useState(false);
  return (
    <ScrollView
      {...props}
      // 醒目注入
      refresherEnabled={props.refresherEnabled}
      refresherTriggered={refreshStatus}
      onRefresherRefresh={() => {
         /**
         提供了refresherTriggered参数后需要显示修改才能触发组件的下拉刷新。
         */
        setStatus(() => true);
        refreshFetcher().finally(() => setStatus(() => false));
      }}
      onTouchMove={(e) => {
        if (scrollTop.current !== 0) {
          e.stopPropagation();
        }
      }}
      onScroll={(e) => {
        scrollTop.current = e.detail.scrollTop;
      }}
    >
      {children}
    </ScrollView>
  );
};

然后我们这样调用:

tsx 复制代码
    <PullDownScrollView
        className='flex-column'
        scrollY
        style={{ height: '100%' }}
        onScrollToLower={() => fetchList(filterParams)}
        refresherEnabled
        refreshFetcher={() => fetchList(filterParams, true)}
      >

这样,即可在微信小程序端实现:

  1. 下拉页面其他部分,整个页面下拉刷新。
  2. 从滚动组件顶部下拉,滚动组件出现下拉刷新。
  3. h5端不会出现滚动组件的下拉刷新,只存在整个页面下拉刷新。

当然,如果需要保持一致,taro提供了一些下拉刷新的参数可以进行调整;逻辑上可以实现隐藏滚动组件的下拉刷新并且手动触发(Taro.startPullDownRefresh)页面的下拉刷新的。不过除了样式问题还有页面的过渡问题,比如下拉滚动区只有滚动区会往下过渡,此时手动触发页面刷新界面交互会非常混乱。

scrollView 中存在tab的情况

这个是一个非常特殊的情况,它的组件逻辑是:

tsx 复制代码
 <ScrollView>
     <Tab> </Tab>
 </ScrollView>

在这种情况下会触发bug:

  1. 进入页面,展示tab A,tab A高度 100px,此时滚动区可滚动高度100px。
  2. 切换到tab B,tab B高度 200px,此时滚动区可滚动高度300px,出现白色不可选的幽灵区。

这个bug的原因非常简单,就是微信小程序会计算position 不是static的节点,而taro的tab切换实现是把其他tab的position换成absolute,然后给一个很大的left等参数隐藏。当然我们没办法修改

网上提供了两种解决方法

  1. 修改组件逻辑,把ScrollView组件放到tab组件内,有多少个tab就有多少个ScrollView组件。
  2. 不再使用ScrollView,使用View自己实现一个CustomScrollView。

但是这里不得不夸一下taro官方,只要你看过react的官方文档,就应该知道修改一个组件的key值,会强制该组件重新渲染。依靠该思路,当我们给ScrollView尝试设置key值时,组件的jsdoc提示我们:

taro官方早就提供过解决方案了。这里我们做一下优化:

tsx 复制代码
    const isWeapp=()=>Taro.getEnv() === Taro.ENV_TYPE.WEAPP;
    const [curIndex,setIndex]=useState(defaultIndex);
    const modifiedScrollKey=isWeapp()?curIndex:'someThingNotChanged';
    return <ScrollView
    key={modifiedScrollKey}
    >
    {...}
    </ScrollView>

最终效果

  • h5端所有情况正常。
  • 微信小程序端所有情况正常(除了有两种下拉方式)。

最终的封装代码:

tsx 复制代码
import { FC, memo, useRef, useState } from 'react';
import { ScrollView, View, ScrollViewProps } from '@tarojs/components';
import Taro from '@tarojs/taro';
interface Props extends ScrollViewProps {
  /** 仅针对微信小程序的下拉刷新适配 */
  refreshFetcher?: () => Promise<any>;
}

/**
 * @description taro自身缺陷,同时开启页面下拉刷新时,滚动区无法上去,
 * 根据issue13697的方案进行polyfill。
 * 请注意小程序的下拉刷新和想象中的有些不同!需要额外注入参数实现
 * @link https://github.com/NervJS/taro/issues/13697
 */
const Index: FC<Props> = (props) => {
  const { children,  refreshFetcher = Promise.resolve } = props;
  const scrollTop = useRef(0);
  const [refreshStatus, setStatus] = useState(false);
  return (
    <ScrollView
      {...props}
      refresherEnabled={props.refresherEnabled}
      refresherTriggered={refreshStatus}
      onRefresherRefresh={() => {
        setStatus(() => true);
        refreshFetcher().finally(() => setStatus(() => false));
      }}
      onTouchMove={(e) => {
        if (scrollTop.current !== 0) {
          e.stopPropagation();
        }
      }}
      onScroll={(e) => {
        scrollTop.current = e.detail.scrollTop;
      }}
    >
      {children}
    </ScrollView>
  );
};
export default memo(Index);
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax