uni-app 使用v-show轮流加载使用v-for渲染的uni-grid时会出现加载不全的问题 并且官方示例也复现了问题

发布于 1周前 作者 ionicwang 来自 Uni-App

uni-app 使用v-show轮流加载使用v-for渲染的uni-grid时会出现加载不全的问题 并且官方示例也复现了问题

产品分类

uniapp/H5

开发环境信息

项目 信息
PC开发环境操作系统 Windows
PC开发环境版本号 win11
HBuilderX类型 正式
HBuilderX版本号 4.36
浏览器平台 Chrome
浏览器版本 Microsoft Edge 版本 126.0.2592.87 (正式版本) (64 位)
项目创建方式 HBuilderX

示例代码

<template>  
    <view class="viewport">  
        <!-- 推荐封面图 -->  
        <view class="cover">  
            <image :src="bannerPicture"></image>  
        </view>  
        <!-- 推荐选项 -->  
        <view class="tabs">  
            <text  
                v-for="(item, index) in subTypes"  
                :key="item.id"  
                class="text"  
                :class="{ active: index === activeIndex }"  
                @tap="activeIndex = index"  
            >  
                {{ item.title }}  
            </text>  
        </view>  
        <!-- 推荐列表 -->  
        <scroll-view  
            v-for="(item, index) in subTypes"  
            :key="item.id"  
            v-show="activeIndex === index"  
            scroll-y  
            class="scroll-view"  
            @scrolltolower="onScrolltolower"  
        >  
            <uni-grid :column="2" :showBorder="false" :square="false">  
                <uni-grid-item v-for="(goods, index2) in item.goodsItems.items" :key="goods.id">  
                    <view class="goods">  
                        <navigator hover-class="none" class="navigator" :url="`/pages/goods/goods?id=${goods.id}`">  
                            <image class="thumb" :src="goods.picture"></image>  
                            <view class="name ellipsis">{{ goods.name }}</view>  
                            <view class="price">  
                                <text class="symbol">¥</text>  
                                <text class="number">{{ goods.price }}</text>  
                            </view>  
                        </navigator>  
                    </view>  
                </uni-grid-item>  
            </uni-grid>  
            <view class="loading-text">  
                {{ item.finish ? '没有更多数据了~' : '正在加载...' }}  
            </view>  
        </scroll-view>  
    </view>  
</template>  

<script setup lang="ts">  
import { getHotListAllAPI } from '../../services/home';  
import { onLoad, onReady } from '@dcloudio/uni-app';  
import { ref } from 'vue';  

// 热门推荐页 标题和url  
const hotMap = [  
    { type: '1', title: '特惠推荐', url: '/hot/preference' },  
    { type: '2', title: '爆款推荐', url: '/hot/inVogue' },  
    { type: '3', title: '一站买全', url: '/hot/oneStop' },  
    { type: '4', title: '新鲜好物', url: '/hot/new' },  
];  

// uniapp 获取页面参数  
const query = defineProps<{  
    type: string;  
}>();  
// 获取当前推荐信息  
const currHot = hotMap.find((v) => v.type === query.type);  
// 动态设置标题  
uni.setNavigationBarTitle({ title: currHot!.title });  

// 推荐封面图  
const bannerPicture = ref('');  
// 推荐选项  
const subTypes = ref<(SubTypeItem & { finish?: boolean })[]>([]);  
// 高亮的下标  
const activeIndex = ref(0);  
// 获取热门推荐数据  
const getHotRecommendData = async () => {  
    const res = await getHotListAllAPI(currHot!.url, {  
        // 技巧:环境变量,开发环境,修改初始页面方便测试分页结束  
        page: 1,  
        pageSize: 10,  
    });  
    // 保存封面  
    bannerPicture.value = res.data.result.bannerPicture;  
    // 保存列表  
    subTypes.value = res.data.result.subTypes;  
};  

// 页面加载  
onReady(() => {  
    getHotRecommendData();  
});  

// 滚动触底  
const onScrolltolower = async () => {  
    // 获取当前选项  
    const currsubTypes = subTypes.value[activeIndex.value];  
    // 分页条件  
    if (currsubTypes.goodsItems.page < currsubTypes.goodsItems.pages) {  
        // 当前页码累加  
        currsubTypes.goodsItems.page++;  
    } else {  
        // 标记已结束  
        currsubTypes.finish = true;  
        // 退出并轻提示  
        return uni.showToast({ icon: 'none', title: '没有更多数据了~' });  
    }  

    // 调用API传参  
    const res = await getHotListAllAPI(currHot!.url, {  
        subType: currsubTypes.id,  
        page: currsubTypes.goodsItems.page,  
        pageSize: currsubTypes.goodsItems.pageSize,  
    });  
    // 新的列表选项  
    const newsubTypes = res.data.result.subTypes[activeIndex.value];  
    // 数组追加  
    currsubTypes.goodsItems.items.push(...newsubTypes.goodsItems.items);  
};  
</script>

操作步骤

首先使用 v-for 加载多个外层 view,外层 view 中包裹了 uni-grid(多个 uni-grid 独立),在外层 view 中添加 v-show 属性条件为 current == index,添加一个按钮可以切换 current

预期结果

多个 grid 显示内容相同。

实际结果

一个 grid 能显示 10 条,第二个 grid 只能显示最后一条。

bug 描述

使用 v-show 轮流加载 v-for 渲染的 uni-grid 时,会出现加载不全的问题,原本两个 tag 中都是 10 条数据,但是第一个加载的能正常显示 10 条数据,第二个就只能显示最后一条数据,并且如果去掉 v-show,或者去掉 uni-grid 会发现两个 v-for 中的数据都是正常的。

Image 1 Image 2 Image 3


2 回复

提供个完整工程,或者简化一下代码,我无法直接运行


在uni-app中使用v-showv-for结合来轮流加载uni-grid时,确实可能会遇到渲染不全的问题。这通常是由于Vue的异步更新机制和DOM渲染顺序导致的。为了解决这个问题,我们可以通过使用Vue的nextTick方法来确保DOM更新完成后再进行操作,或者通过调整逻辑,使用v-if来控制元素的渲染,因为v-if会完全销毁和重建元素,这样可以避免一些由于DOM状态不一致导致的问题。

下面是一个使用v-if代替v-show并结合定时器来轮流显示uni-grid的示例代码:

<template>
  <view>
    <uni-grid :column="3">
      <uni-grid-item v-for="(item, index) in items" :key="index" v-if="currentIndex === index">
        <view class="grid-content">{{ item }}</view>
      </uni-grid-item>
    </uni-grid>
  </view>
</template>

<script>
export default {
  data() {
    return {
      items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'],
      currentIndex: 0,
      interval: null,
    };
  },
  mounted() {
    this.startRotation();
  },
  beforeDestroy() {
    clearInterval(this.interval);
  },
  methods: {
    startRotation() {
      this.interval = setInterval(() => {
        this.currentIndex = (this.currentIndex + 1) % this.items.length;
      }, 2000); // 每2秒切换一次
    },
  },
};
</script>

<style>
.grid-content {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100px;
  background-color: #f0f0f0;
  border: 1px solid #ddd;
}
</style>

在这个示例中,我们使用v-if来控制uni-grid-item的渲染,确保每次只有一个uni-grid-item被渲染。currentIndex用于跟踪当前应该显示的项的索引,interval用于设置定时器,每2秒更新一次currentIndex

注意,我们在beforeDestroy生命周期钩子中清除了定时器,以避免在组件销毁后继续执行定时任务,导致内存泄漏。

这种方法虽然使用了v-if而不是v-show,但可以有效地解决轮流加载时渲染不全的问题,因为v-if会确保在每次切换时都重新渲染DOM元素,从而避免由于Vue的异步更新机制导致的问题。

回到顶部