You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pinia+Vue分页场景:数组slice方法动态参数调用失效

问题:分页切片动态参数返回空数组的原因与解决办法

当点击分页按钮时,动态计算的start/end对pokeStore.pokemonsLoaded执行slice始终返回空数组,但使用静态数值(如slice(2,4))能正常获取结果,控制台显示数组已更新且参数范围正确。


相关代码展示

Pinia存储代码

import { ref, computed, shallowRef } from 'vue'
import { defineStore } from 'pinia'

export const usePokemonStore = defineStore('pokemons', () => {
    // 定义状态
    const pokemonsList = ref([]);
    const pokemonsLoaded = ref([]);
    const pokemonsLoadedNames = ref([]);

    // 计算属性
    const pokemonsListLength = computed(() => pokemonsList.value.length)
    const pokemonsLoadedLength = computed(() => pokemonsLoaded.value.length)

    // 动作函数
    async function getList() {
        const res = await fetch('https://pokeapi.co/api/v2/pokemon?limit=100000&offset=0');
        const data = await res.json();
        pokemonsList.value = data["results"];
    }

    async function loadPokemon(name) {
        const URI = `https://pokeapi.co/api/v2/pokemon/${name}`
        const res = await fetch(URI);
        const data = await res.json();
        pokemonsLoadedNames.value.push(data["name"])
        pokemonsLoaded.value.push(data)
    }

    async function loadPokemons(offset, limit){
        // 原有逻辑(存在问题)
        limit = limit > pokemonsListLength ? pokemonsListLength : limit;
        limit = limit < 0 ? 10 : limit

        offset = offset < 0 ? 0 : offset;
        offset = offset > pokemonsListLength ? 0 : offset

        for (let i = offset; i < offset+limit; i++){
            if (pokemonsLoadedNames.value.includes(pokemonsList.value[i].name)) {
                continue;
            }
            loadPokemon(pokemonsList.value[i].name)
        }
    }

    return {
        pokemonsList, 
        pokemonsLoaded, 
        pokemonsListLength, 
        pokemonsLoadedLength, 
        pokemonsLoadedNames, 
        getList,
        loadPokemon,
        loadPokemons
    }
})

Vue分页组件代码

<template>
    <div class="pokedex">
        <PokemonImage class="pokemon-figure" pokemon="" />
        <ul v-if="pokemonsToShow" class="pokemon-showcase">
            <li class="pokemon-item" v-for="pokemon in pokemonsToShow">
                <PokemonCard :pokemon="pokemon" />
            </li>
        </ul>
        <div class="navigation">
            <button v-show="page !== 1" @click="pageChange(-1)">Previous Page</button>
            <button @click="pageChange(1)">Next Page</button>
        </div>
        {{ page }}
    </div>
</template>

<script setup>
import { onBeforeMount, ref, computed, watch } from 'vue';
import { usePokemonStore } from '../stores/pokemon'
import PokemonCard from '../components/PokemonCard.vue'
import PokemonImage from '../components/PokemonImage.vue'
const pokeStore = usePokemonStore();

const page = ref(1)
const pokemonsToShow = ref([])

// 基于页码计算偏移量和每页数量
const limit = computed(() => 20 );
const offset = computed(() => page.value * limit.value - limit.value);

// 初始加载
onBeforeMount(async () => {
    await pokeStore.getList()
    await pokeStore.loadPokemons(0, limit.value)
    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(0, pokeStore.pokemonsLoadedLength)
})

// 分页切换逻辑
const pageChange = async (step) => {
    page.value = page.value + step
    await pokeStore.loadPokemons(offset.value, limit.value)

    const start = offset.value;
    const end = offset.value + limit.value;
    pokemonsToShow.value = pokeStore.pokemonsLoaded.slice(start, end)
}
</script>

问题原因分析

  1. 异步等待缺失:loadPokemons循环中调用异步的loadPokemon但未等待其完成,函数会提前返回。组件中await pokeStore.loadPokemons(...)实际并未等所有宝可梦加载完成就执行slice,此时目标分页的数据还未存入数组。
  2. 数组顺序不匹配:pokemonsLoaded按请求完成顺序push数据,而非宝可梦在列表中的顺序。即使数据加载完成,slice取到的数组索引范围元素,与当前分页的宝可梦不一定对应。
  3. Computed引用错误:pokemonsListLength是computed ref,但loadPokemons中直接用pokemonsListLength而非pokemonsListLength.value做数值比较,导致逻辑判断失效。

解决方案

1. 修复Pinia的loadPokemons方法

  • 修正computed ref的访问方式(添加.value)
  • 使用Promise.all等待所有加载请求完成
  • 保证异步操作的顺序与完整性

修改后的loadPokemons代码:

async function loadPokemons(offset, limit){
    // 修正数值比较逻辑
    limit = limit > pokemonsListLength.value ? pokemonsListLength.value : limit;
    limit = limit < 0 ? 10 : limit

    offset = offset < 0 ? 0 : offset;
    offset = offset > pokemonsListLength.value ? 0 : offset

    const promises = [];
    for (let i = offset; i < offset+limit; i++){
        const pokemonName = pokemonsList.value[i].name;
        if (pokemonsLoadedNames.value.includes(pokemonName)) {
            continue;
        }
        // 收集所有请求Promise
        promises.push(loadPokemon(pokemonName));
    }
    // 等待所有请求完成
    await Promise.all(promises);
}

2. 重构组件的pokemonsToShow为计算属性

不再依赖数组索引切片,而是根据当前分页的宝可梦名称筛选已加载数据,保证数据与分页严格对应:

修改后的组件代码片段:

// 替换原有pokemonsToShow定义为计算属性
const pokemonsToShow = computed(() => {
  // 获取当前分页对应的宝可梦名称列表
  const currentPageNames = pokeStore.pokemonsList.slice(offset.value, offset.value + limit.value).map(item => item.name);
  // 从已加载数据中筛选对应名称的宝可梦
  return pokeStore.pokemonsLoaded.filter(pokemon => currentPageNames.includes(pokemon.name));
});

// 简化初始加载逻辑
onBeforeMount(async () => {
    await pokeStore.getList()
    await pokeStore.loadPokemons(0, limit.value)
});

// 简化分页切换逻辑
const pageChange = async (step) => {
    page.value = page.value + step
    await pokeStore.loadPokemons(offset.value, limit.value)
    // 无需手动赋值,计算属性会自动更新
}

额外优化建议

可以在Pinia中维护一个pokemonsMap对象,以宝可梦名称为键存储数据,提升筛选效率:

// 在Pinia中添加状态
const pokemonsMap = ref({});

// 修改loadPokemon函数
async function loadPokemon(name) {
    const URI = `https://pokeapi.co/api/v2/pokemon/${name}`
    const res = await fetch(URI);
    const data = await res.json();
    pokemonsLoadedNames.value.push(data.name);
    pokemonsLoaded.value.push(data);
    pokemonsMap.value[data.name] = data; // 存入Map
}

// 组件中优化筛选逻辑
const pokemonsToShow = computed(() => {
  return pokeStore.pokemonsList.slice(offset.value, offset.value + limit.value)
    .map(item => pokeStore.pokemonsMap[item.name])
    .filter(Boolean); // 过滤未加载完成的项
});

内容的提问来源于stack exchange,提问作者user21172353

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 03:20:52