Vue3 Pinia远程数据获取优化方案

在 Vue 3 中使用 Pinia 时,如果你希望在多个地方同时调用同一个远程数据获取方法时,只有第一次调用真正执行远程请求,而后续的调用等待第一次请求的结果并共享该结果,你可以通过以下方式实现:

1. 使用 Promise 和缓存机制

你可以在 Pinia store 中维护一个 Promise 对象,用于缓存远程请求的结果。当第一次调用时,发起请求并将 Promise 存储起来。后续的调用直接返回这个 Promise,从而实现只发起一次请求的效果。

2. 实现代码示例

假设你有一个 useUserStore 的 Pinia store,用于获取用户信息:

import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';

export const useUserStore = defineStore('user', () => {
  const userData = ref(null);
  let fetchPromise = null;

  const fetchUserData = async () => {
    if (fetchPromise) {
      // 如果已经有正在进行的请求,直接返回该 Promise
      return fetchPromise;
    }

    // 发起远程请求
    fetchPromise = axios.get('/api/user')
      .then(response => {
        userData.value = response.data;
        return userData.value;
      })
      .finally(() => {
        // 请求完成后重置 fetchPromise
        fetchPromise = null;
      });

    return fetchPromise;
  };

  return {
    userData,
    fetchUserData,
  };
});

3. 使用示例

在组件中使用 fetchUserData 方法时,无论调用多少次,只有第一次会真正发起请求,后续的调用会等待第一次请求的结果:



4. 解释

  • fetchPromise: 用于存储当前正在进行的请求的 Promise 对象。如果 fetchPromise 已经存在,说明已经有请求在进行中,直接返回该 Promise

  • finally: 在请求完成后(无论成功或失败),重置 fetchPromise 为 null,以便下次调用时可以重新发起请求。

5. 注意事项

  • 如果请求失败,fetchPromise 也会被重置,以便下次调用时可以重新发起请求。

  • 如果你希望在一定时间内缓存数据,可以结合 setTimeout 或其他缓存机制来实现。

通过这种方式,你可以确保在多个地方同时调用远程数据获取方法时,只有第一次调用会真正发起请求,后续的调用会等待第一次请求的结果并共享该结果。

如果你使用的是 Option Store 而不是 Setup Store,写法会稍有不同,但核心逻辑是一样的。你可以在 state 中定义数据,在 actions 中定义方法,并通过一个 Promise 缓存机制来实现多次调用时只发起一次请求的效果。

以下是基于 Option Store 的实现方式:

1. Option Store 实现

import { defineStore } from 'pinia';
import axios from 'axios';

export const useUserStore = defineStore('user', {
  state: () => ({
    userData: null, // 存储用户数据
    fetchPromise: null, // 存储当前的请求 Promise
  }),

  actions: {
    async fetchUserData() {
      // 如果已经有正在进行的请求,直接返回该 Promise
      if (this.fetchPromise) {
        return this.fetchPromise;
      }

      // 发起远程请求
      this.fetchPromise = axios.get('/api/user')
        .then(response => {
          this.userData = response.data; // 更新 state 中的数据
          return this.userData;
        })
        .finally(() => {
          // 请求完成后重置 fetchPromise
          this.fetchPromise = null;
        });

      return this.fetchPromise;
    },
  },
});

2. 使用示例

在组件中使用时,逻辑与之前类似:



3. 核心逻辑解释

  1. state:

    • userData: 用于存储从远程获取的用户数据。

    • fetchPromise: 用于存储当前正在进行的请求的 Promise 对象。

  2. actions:

    • fetchUserData: 这是一个异步方法,用于获取用户数据。

      • 如果 fetchPromise 已经存在(即有请求正在进行),直接返回该 Promise

      • 如果 fetchPromise 为 null,则发起新的请求,并将 Promise 存储到 fetchPromise 中。

      • 请求完成后,无论成功还是失败,都会在 finally 中重置 fetchPromise 为 null,以便下次调用时可以重新发起请求。

  3. 组件中使用:

    • 在 onMounted 钩子中调用 fetchUserData 方法。

    • 通过 await 等待请求完成,并更新 loading 状态。

4. 注意事项

  • 错误处理:

    • 如果请求失败,fetchPromise 也会被重置,以便下次调用时可以重新发起请求。

    • 你可以在 catch 中处理错误逻辑。

  • 缓存机制:

    • 如果你希望在一定时间内缓存数据,可以在 state 中添加一个 lastFetchTime 字段,并在 fetchUserData 中检查是否在缓存时间内。如果是,则直接返回缓存的数据。

export const useUserStore = defineStore('user', {
  state: () => ({
    userData: null,
    fetchPromise: null,
    lastFetchTime: null, // 上次请求的时间
  }),

  actions: {
    async fetchUserData() {
      const now = Date.now();
      const cacheDuration = 5 * 60 * 1000; // 缓存 5 分钟

      // 如果缓存未过期,直接返回缓存的数据
      if (this.lastFetchTime && now - this.lastFetchTime < cacheDuration) {
        return this.userData;
      }

      // 如果已经有正在进行的请求,直接返回该 Promise
      if (this.fetchPromise) {
        return this.fetchPromise;
      }

      // 发起远程请求
      this.fetchPromise = axios.get('/api/user')
        .then(response => {
          this.userData = response.data;
          this.lastFetchTime = now; // 更新上次请求时间
          return this.userData;
        })
        .finally(() => {
          this.fetchPromise = null;
        });

      return this.fetchPromise;
    },
  },
});

通过这种方式,你可以在 Option Store 中实现多次调用时只发起一次请求的效果,并且可以灵活地扩展缓存机制。

如果你的 fetchUserData 需要请求多个接口,并且每个接口都已经封装好了异步函数(可以直接用 await 调用),那么你可以通过以下方式实现:

  1. 确保多个接口的请求是并发的:使用 Promise.all 来同时发起多个请求。

  2. 缓存机制:仍然使用一个 Promise 来缓存整个请求过程,确保多次调用时只发起一次请求。

以下是基于 Option Store 的实现方式:


1. Option Store 实现

假设你有两个异步函数 fetchUserInfo 和 fetchUserOrders,分别用于获取用户信息和用户订单:

import { defineStore } from 'pinia';

// 假设这是封装好的异步函数
async function fetchUserInfo() {
  const response = await fetch('/api/user/info');
  return response.json();
}

async function fetchUserOrders() {
  const response = await fetch('/api/user/orders');
  return response.json();
}

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null, // 存储用户信息
    userOrders: null, // 存储用户订单
    fetchPromise: null, // 存储当前的请求 Promise
  }),

  actions: {
    async fetchUserData() {
      // 如果已经有正在进行的请求,直接返回该 Promise
      if (this.fetchPromise) {
        return this.fetchPromise;
      }

      // 发起多个请求
      this.fetchPromise = Promise.all([fetchUserInfo(), fetchUserOrders()])
        .then(([userInfo, userOrders]) => {
          // 更新 state 中的数据
          this.userInfo = userInfo;
          this.userOrders = userOrders;
          return { userInfo, userOrders }; // 返回结果
        })
        .finally(() => {
          // 请求完成后重置 fetchPromise
          this.fetchPromise = null;
        });

      return this.fetchPromise;
    },
  },
});

2. 使用示例

在组件中使用时,逻辑与之前类似:



3. 核心逻辑解释

  1. fetchUserData 方法:

    • 使用 Promise.all 并发请求多个接口(fetchUserInfo 和 fetchUserOrders)。

    • 将结果分别存储到 state 中的 userInfo 和 userOrders

    • 返回一个 Promise,确保多次调用时只发起一次请求。

  2. 缓存机制:

    • 使用 fetchPromise 缓存当前的请求 Promise

    • 如果 fetchPromise 已经存在,直接返回该 Promise,避免重复请求。

  3. 组件中使用:

    • 在 onMounted 钩子中调用 fetchUserData 方法。

    • 通过 await 等待请求完成,并更新 loading 状态。


4. 扩展:支持动态参数

如果你的 fetchUserData 需要根据参数动态请求不同的数据,可以将参数传递给 fetchUserData,并在缓存时考虑参数的影响。

例如:

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    userOrders: null,
    fetchPromises: new Map(), // 使用 Map 缓存不同参数的请求
  }),

  actions: {
    async fetchUserData(userId) {
      // 如果已经有相同参数的请求,直接返回该 Promise
      if (this.fetchPromises.has(userId)) {
        return this.fetchPromises.get(userId);
      }

      // 发起多个请求
      const promise = Promise.all([fetchUserInfo(userId), fetchUserOrders(userId)])
        .then(([userInfo, userOrders]) => {
          this.userInfo = userInfo;
          this.userOrders = userOrders;
          return { userInfo, userOrders };
        })
        .finally(() => {
          // 请求完成后从缓存中移除
          this.fetchPromises.delete(userId);
        });

      // 缓存当前请求
      this.fetchPromises.set(userId, promise);

      return promise;
    },
  },
});

在组件中使用时,可以传递参数:

onMounted(async () => {
  try {
    await userStore.fetchUserData(123); // 传递 userId
  } catch (error) {
    console.error('Failed to fetch user data:', error);
  } finally {
    loading.value = false;
  }
});

5. 总结

  • 使用 Promise.all 可以并发请求多个接口。

  • 通过 fetchPromise 缓存机制,确保多次调用时只发起一次请求。

  • 如果需要支持动态参数,可以使用 Map 来缓存不同参数的请求。

这种方式既高效又灵活,适合复杂的异步数据获取场景。

你可能感兴趣的:(前端,vue.js,javascript)