Nuxt3中用Pinia存储服务端API初始状态及解决filter报错问题
解决Pinia中数组filter报错及优化数据缓存方案
错误原因
- Store中
data初始值设为null,组件渲染时restaurants.value仍为null,直接调用filter方法触发Cannot read properties of null (reading 'filter')错误。 callData是异步方法,组件初始化阶段API请求尚未完成,data保持初始null状态,导致同步执行的filter调用失败。
修复步骤
1. 修正Store初始值与API返回处理
将Store中data的初始值改为空数组,并处理API返回可能为空的情况:
// store/restaurants.ts export const useRestaurantsStore = defineStore( "restaurants", { state: () => ({ data: [], // 替换null为[] }), actions: { async callData() { const { data } = await useFetch('/api/restaurants'); // 兜底处理避免数据为空 this.data = data.value?.items || []; }, }, });
2. 用计算属性处理过滤逻辑
组件中使用computed替代ref,利用响应式自动跟踪数据变化,API返回后自动重新计算过滤结果:
<!-- pages/restaurants/[slug].vue --> <template> <div> <!-- 可选:添加加载状态提示 --> <div v-if="restaurants.length === 0">加载中...</div> <ul class="c-cardList" v-else> <li class="c-card" v-for="item in filteredRestaurants" :key="item.id"> <!-- 用可选链避免深层属性访问报错 --> <div class="c-card_name">{{ item.data[0]?.data }}</div> </li> </ul> </div> </template> <script setup> import { computed } from 'vue'; const { data: restaurants } = useRestaurants(); const category = "cafe"; // 计算属性自动响应数据变化 const filteredRestaurants = computed(() => { return restaurants.value.filter(element => { return element.data[1]?.data[0] === category; }); }); </script>
3. 避免重复API请求(可选)
在Composable中判断数据是否已存在,仅当数据为空时调用API:
// composables/useRestaurants.ts import { storeToRefs } from "pinia"; import { useRestaurantsStore } from '@/store/restaurants'; export default async function () { const restaurantsStore = useRestaurantsStore(); // 已有数据则跳过请求 if (restaurantsStore.data.length === 0) { await restaurantsStore.callData(); } const { data } = storeToRefs(restaurantsStore); return { data }; }
组件中需用await调用:
<script setup> import { computed } from 'vue'; const { data: restaurants } = await useRestaurants(); const category = "cafe"; const filteredRestaurants = computed(() => { return restaurants.value.filter(element => element.data[1]?.data[0] === category); }); </script>
更优实现方案
1. 预处理嵌套数据
针对后端返回的深层嵌套结构,在Store中提前提取所需字段,简化组件逻辑并降低报错风险:
// store/restaurants.ts actions: { async callData() { if (this.data.length > 0) return; const { data, error } = await useFetch('/api/restaurants'); if (error.value) { console.error('加载餐厅数据失败:', error.value); this.data = []; return; } // 预处理数据,扁平化结构 this.data = data.value?.items.map(item => ({ id: item.id, name: item.data[0]?.data || '未知名称', category: item.data[1]?.data[0] || '未知分类' })) || []; }, },
组件中可直接使用简化后的字段:
<script setup> const { data: restaurants } = useRestaurants(); const category = "cafe"; const filteredRestaurants = computed(() => { return restaurants.value.filter(item => item.category === category); }); </script> <template> <li class="c-card" v-for="item in filteredRestaurants" :key="item.id"> <div class="c-card_name">{{ item.name }}</div> </li> </template>
2. 添加加载与错误状态
在Store中维护加载状态和错误信息,提升用户体验:
// store/restaurants.ts state: () => ({ data: [], isLoading: false, error: null }), actions: { async callData() { if (this.data.length > 0) return; this.isLoading = true; this.error = null; try { const { data } = await useFetch('/api/restaurants'); this.data = data.value?.items.map(item => ({ id: item.id, name: item.data[0]?.data || '未知名称', category: item.data[1]?.data[0] || '未知分类' })) || []; } catch (err) { this.error = '加载餐厅数据失败,请稍后重试'; console.error(err); } finally { this.isLoading = false; } }, },
组件中使用状态提示用户:
<template> <div> <div v-if="isLoading">加载中...</div> <div v-if="error" class="error">{{ error }}</div> <ul class="c-cardList" v-else> <li class="c-card" v-for="item in filteredRestaurants" :key="item.id"> <div class="c-card_name">{{ item.name }}</div> </li> </ul> </div> </template> <script setup> import { computed } from 'vue'; const { data: restaurants, isLoading, error } = useRestaurants(); const category = "cafe"; const filteredRestaurants = computed(() => { return restaurants.value.filter(item => item.category === category); }); </script>
内容的提问来源于stack exchange,提问作者deanocean
相关产品推荐
相关产品推荐

