Nuxt3 + Pinia:第三方API调用模块化及SSR适配方案咨询
在Nuxt3中实现Pinia存储API数据并支持SSR与用户独立数据的最佳实践
问题背景
我希望将API路由与调用逻辑、页面代码分离,把API调用结果存储在Pinia Store中,并可按需刷新或修改数据。常规Vue3项目中的简化实现如下:
helpers/requests.js
import axios from 'axios'; const someApiCall = function(options) { return new Promise((resolve, reject) => { const url = new URL('https://api.publicapis.org/entries'); for (const [key, value] of Object.entries(options)) { url.searchParams.set(key, value); } axios .get(url.toString()) .then((response) => { return resolve(response.data); }) .catch((e) => { console.log(e); reject(e); }); }); }; export { someApiCall };
stores/mystore.js
import { defineStore } from 'pinia'; import { someApiCall } from '@/helpers/requests.js'; export const useMyStore = defineStore('myStore', { state: () => ({ loaded: false, count: 0, items: [], }), actions: { // 初始加载Store async init() { if (this.loaded) { return; } await this.nextPage(); }, // 加载下一批数据 async nextPage() { if (this.loaded && this.items.length >= this.count) { return; } const data = await someApiCall({ myOption: 'blabla', }); if (data.results && Array.isArray(data.results)) { this.count = data.count ? data.count : 0; this.items = this.items.concat(data.results); this.loaded = true; } else { console.error('Report Error'); console.log(data); setTimeout(this.nextPage, 5000); } }, async refresh() { this.count = 0; this.items.length = 0; this.loaded = false; await this.nextPage(); }, }, });
components/MyComponent.vue
<script setup> import { onMounted, ref } from 'vue'; import { useMyStore } from '@/stores/index.js'; // Store const myStore = useMyStore (); // 加载动画 const isLoading = ref(true); onMounted(async () => { await myStore.init(); isLoading.value = false; }); const loadMore = async () => { isLoading.value = true; await myStore.nextPage(); isLoading.value = false; }; </script>
现在需要适配Nuxt3,满足两个核心需求:
- 确保每个用户的数据独立(需传递Cookie)
- 支持服务端渲染(SSR)
尝试过程中遇到的困境:
useFetch和useAsyncData只能在<script setup>中使用- 曾误以为
$fetch仅适用于server目录下的API,后来发现可在<script setup>外使用,已成功将数据存入Pinia并实现SSR,但不确定是否为最佳实践,寻求优化建议。
优化方案与最佳实践
1. 用Nuxt3内置$fetch替代Axios
Nuxt3内置的$fetch基于ofetch封装,支持客户端/服务端双环境,能自动处理Cookie传递(服务端会从当前请求头获取Cookie并转发,客户端自动携带域名Cookie),完全适配外部API调用场景。
修改helpers/requests.js:
// helpers/requests.js export const someApiCall = async (options) => { const url = new URL('https://api.publicapis.org/entries'); Object.entries(options).forEach(([key, value]) => { url.searchParams.set(key, value); }); // credentials: 'include' 确保Cookie在两端正确传递 return await $fetch(url.toString(), { credentials: 'include' }); };
2. Pinia Store适配Nuxt3 SSR环境
Nuxt3的Pinia会自动处理服务端状态hydration,只需在Store中规范异步逻辑即可。
优化后的stores/mystore.js:
// stores/mystore.js import { defineStore } from 'pinia'; import { someApiCall } from '@/helpers/requests'; export const useMyStore = defineStore('myStore', { state: () => ({ loaded: false, count: 0, items: [], retryCount: 0 // 新增重试次数控制 }), actions: { async init() { if (this.loaded) return; await this.nextPage(); }, async nextPage() { if (this.loaded && this.items.length >= this.count) return; try { const data = await someApiCall({ myOption: 'blabla' }); if (data.results && Array.isArray(data.results)) { this.count = data.count ?? 0; // 用扩展运算符替代concat,保持响应式稳定性 this.items = [...this.items, ...data.results]; this.loaded = true; this.retryCount = 0; // 成功后重置重试次数 } else { console.error('无效的API响应结构:', data); this._retryRequest(); } } catch (e) { console.error('API调用失败:', e); this._retryRequest(); } }, async refresh() { this.count = 0; this.items = []; // 用空数组替代修改length,符合响应式规范 this.loaded = false; this.retryCount = 0; await this.nextPage(); }, // 私有方法:处理请求重试 _retryRequest() { if (this.retryCount < 3) { this.retryCount++; setTimeout(() => this.nextPage(), 5000); } } }, });
3. 组件中实现SSR兼容的数据预加载
在Nuxt3中,通过onServerPrefetch实现服务端预渲染数据,同时在客户端用onMounted兜底,确保两种场景下数据都能正确加载:
<!-- components/MyComponent.vue --> <script setup> import { ref, onMounted, onServerPrefetch } from 'vue'; import { useMyStore } from '@/stores/mystore'; const myStore = useMyStore(); const isLoading = ref(true); // 服务端预获取数据,确保SSR渲染时已有内容 onServerPrefetch(async () => { await myStore.init(); isLoading.value = false; }); // 客户端兜底:处理客户端导航或服务端未渲染的情况 onMounted(async () => { if (!myStore.loaded) { await myStore.init(); } isLoading.value = false; }); const loadMore = async () => { isLoading.value = true; await myStore.nextPage(); isLoading.value = false; }; </script>
4. 额外优化建议
- 状态持久化:如果需要用户刷新页面后保留数据,可使用
@pinia-plugin-persistedstate/nuxt插件,但敏感数据禁止存储在本地。 - 请求缓存:在Store中添加缓存逻辑,对相同参数的API请求避免重复调用。
- 全局错误状态:在Store中新增
error状态字段,方便组件统一展示错误提示,而非仅在控制台输出。 - 类型安全:用TypeScript为API响应、Store状态定义类型,提升代码可维护性。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

