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>
问题原因分析
- 异步等待缺失:
loadPokemons循环中调用异步的loadPokemon但未等待其完成,函数会提前返回。组件中await pokeStore.loadPokemons(...)实际并未等所有宝可梦加载完成就执行slice,此时目标分页的数据还未存入数组。 - 数组顺序不匹配:
pokemonsLoaded按请求完成顺序push数据,而非宝可梦在列表中的顺序。即使数据加载完成,slice取到的数组索引范围元素,与当前分页的宝可梦不一定对应。 - 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
相关产品推荐
相关产品推荐

