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);
相关推荐
程序员小杰@31 分钟前
AI前端组件库Ant DesIgn X
开发语言·前端·人工智能
致微1 小时前
Vue项目 bug 解决
前端·vue.js·bug
慕斯策划一场流浪1 小时前
fastGPT—nextjs—mongoose—团队管理之部门相关api接口实现
前端·javascript·html·fastgpt部门创建·fastgpt团队管理·fastgpt部门成员更新·fastgpt部门成员创建
我自纵横20232 小时前
事件处理程序
开发语言·前端·javascript·css·json·ecmascript
坊钰2 小时前
【MySQL 数据库】数据类型
java·开发语言·前端·数据库·学习·mysql·html
我是小路路呀3 小时前
css 文字换行每一个字渐变
前端·css
谢小飞3 小时前
Threejs全球坐标分布效果
前端·three.js
喝拿铁写前端3 小时前
🚀从 0 到 1 构建字段推荐引擎:20+ 工具方法一文打尽!
前端
森叶3 小时前
免费Deepseek-v3接口实现Browser-Use Web UI:浏览器自动化本地模拟抓取数据实录
前端·人工智能·自动化
拉不动的猪3 小时前
刷刷题50(vue3)
前端·javascript·面试