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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 08:40:32