vant4的van-pull-refresh里的列表不在顶部时下拉也会触发刷新的问题

80 阅读2分钟

官方的案例给人误导

<van-pull-refresh v-model="loading" @refresh="onRefresh"> 
<p>刷新次数: {{ count }}</p> 
</van-pull-refresh>

这种结构,不管里面列表会不会滚动,只要下拉一定会触发下拉刷新

文档尾部给的说明

PullRefresh 的触发条件是?

PullRefresh 的触发条件是「父级滚动元素的滚动条在顶部位置」。

  • 如果最近一个可滚动的父级元素是 window,则要求 window.pageYOffset === 0
  • 如果最近一个可滚动的父级元素是 Element,则要求 Element.scrollTop === 0

基于此的改造方案(渲染的列表较短时推荐)

  • van-pull-refresh高度不控制,由内部列表撑开。(可以设置最小高度为列表容器高度)
  • van-pull-refresh外部负责控制滚动
<div class="list-container" >
     <van-pull-refresh
       v-model="loading"
       style="min-height: 100%"
       @refresh="getAccountList"
     >
       <div
         class="account-item"
         v-for="account in accountList"
         :key="account.id"
       >
         <el-avatar
           :size="25"
           :src="account.imgUrl"
         >
           <img src="@/assets/avatar.png" />
         </el-avatar>
         <div class="account-name">{{ account.name }}</div>
       </div>
     </van-pull-refresh>
</div>
<style>
   .list-container {
      height: 100%;
      overflow-y: auto;
      .account-item {
        display: flex;
        align-items: center;
        column-gap: 10px;
        + .account-item {
          margin-top: 15px;
        }
        .account-name {
          word-break: break-all;
        }
      }
    }
</style>

常见的复杂的改造方案(若列表很长要用虚拟列表时)

列表很长渲染阻塞,van-pull-refresh里用了虚拟列表,高度是限死的,此时不能利用van-pull-refresh滚动到父容器顶部的特性来触发加载了

这种情况不需要考虑上拉加载,因为如果要用上拉加载,说明一次拉取的数据不多,单次渲染负担不大,也就没必要用虚拟列表。不用虚拟列表,用上面那个常规方案就行

  • 给van-pull-refresh增加disabled属性
  • van-pull-refresh里面包裹的滚动列表增加scroll监听,滚动到顶部之前,disabled一直为true
<template>
  <div>
    <van-pull-refresh
      v-model="loading"
      :disabled="!canPullRefresh"
      :head-height="100"
      @refresh="getAccountList"
    >
      <template v-if="accountList.length > 0">
        <!-- 虚拟滚动 -->
        <RecycleScroller
          class="chat-scroller"
          :items="accountList"
          :item-size="40"
          key-field="id"
          v-slot="{ item }"
          @scroll="scrollDebounce"
        >
          <div class="account-item">
            <el-avatar :size="25" :src="item.imgUrl">
              <img src="@/assets/avatar.png" />
            </el-avatar>
            <div class="account-name">{{ item.name }}</div>
          </div>
        </RecycleScroller>
      </template>
      <div class="blank-placehold" v-else>暂无数据,可通过下拉页面进行刷新</div>
    </van-pull-refresh>
  </div>
</template>

<script setup>
import { ref } from "vue";
import Mock from "mockjs";
import { throttle } from "lodash-es";

const accountList = ref([]);
const loading = ref(false);
// 模拟获取账号列表
function getAccountList() {
  loading.value = true;
  setTimeout(() => {
    loading.value = false;
    // 定义生成数据的模板
    const dataTemplate = {
      "id|+1": 1, // 自增的id属性
      name: "@cname", // 使用 @cname 来生成中文名
      imgUrl: Mock.Random.image(),
    };

    // 生成包含 10 个对象的数组
    const dataList = Mock.mock({
      "list|100": [dataTemplate],
    });
    accountList.value = dataList.list;
  }, 1000);
}

getAccountList();

// 控制是否允许触发下拉刷新(只有滚到页面顶部时下拉,才可触发刷新)
const canPullRefresh = ref(true);
// 防抖在动作终止时才会更新,若滚到顶部后迅速下拉一段又上拉又下拉,会导致触发下拉刷新。故此处用节流
const scrollDebounce = throttle(scrollList, 100);
function scrollList(e) {
  const scrollTop = e.target.scrollTop;
  // 只有在顶部时才允许触发刷新
  canPullRefresh.value = scrollTop <= 0;
}
</script>

<style lang="scss" scoped>
.chat-scroller {
  height: 50vh;
  .account-item {
    display: flex;
    align-items: center;
    height: 40px;
    column-gap: 10px;
  }
}
</style>