使用vuex从api获取数据的最佳实践

时间:2018-07-01 08:34:20

标签: vue.js vuex

有两个页面(或有关vue术语的组件)都需要相同的数据集,这是通过http上的api提供的。这两个组件的访问顺序是不确定的(或取决于用户的行为),并且数据仅应获取一次,因为它不会发生很多变化。

我知道一个想法,一个state存储实际数据,mutation突变state,而action作为异步请求进行肮脏的工作,多变异协调等。

问题是:如上所述,执行某些缓存逻辑的最佳实践是什么?

我想出了以下三种方法,但是它们对我来说都不是完美的:

缺点:

  1. 在访问任何地方的数据之前,我需要先调度操作,因为我不知道数据是否已经被提取。

    // ComponentA
    async mouted () {
        await this.store.dispatch('fetchData')
        this.someData = this.store.state.someData
    }
    
    // ComponentB
    async mouted () {
        await this.store.dispatch('fetchData')
        this.someData = this.store.state.someData
    }
    
    // vuex action
    {
       async fetchData ({ state, commit }) {
           // handles the cache logic here
           if (state.someData) return
           commit('setData', await apis.fetchData())
       }
    }
    
  2. 缓存逻辑分散在整个代码库中–臭〜

    // ComponentA
    async mouted () {
        if (this.store.state.someData === undefined) {
            // handles the cache logic
            await this.store.dispatch('fetchData')
        }
        this.someData = this.store.state.someData
    }
    
    // ComponentB
    async mouted () {
        if (this.store.state.someData === undefined) {
            // handles the cache logic
            await this.store.dispatch('fetchData')
        }
        this.someData = this.store.state.someData
    }
    
    // vuex action
    {
       async fetchData ({ state, commit }) {
           commit('setData', await apis.fetchData())
       }
    }
    
  3. 在这三个方面也许是最完美的,但是使用动作分派的返回值作为数据时,我感到有些奇怪。随着存储的增长,缓存逻辑将散布在所有动作上(会有越来越多的动作重复相同的缓存逻辑)

    // ComponentA
    async mouted () {
        this.someData = await this.store.dispatch('fetchData')
    }
    
    // ComponentB
    async mouted () {
        this.someData = await this.store.dispatch('fetchData')
    }
    
    // vuex action
    {
       async fetchData ({ state, commit }) {
           if (!state.someData) commit('setData', await apis.fetchData())
           return state.someData
       }
    }
    

我希望将缓存逻辑放入“独立于vue&vuex”的网络层。但随后,网络层的“缓存”部分可能会成为另一个“ vuex”存储。 XD

2 个答案:

答案 0 :(得分:1)

我最近遇到了类似的问题。并且弄清楚了,最好还是在动作中进行承诺。这有助于坚持vuex的生命周期。因此您的代码可能如下所示:

{
    async fetchData ({ state, commit }) {
       if (!state.someData) commit('setData', await apis.fetchData())
       commit('setData', state.someData)
    }
 }

然后使用getters在组件中使用state.someData而不是分配它。

答案 1 :(得分:0)

我觉得我在重新发明轮子,应该有更好的现成的东西来做到这一点,但这是我的轮子,它对我来说很好。

我回复是为了回馈,但也希望有人告诉我更好的方法!

我的方法通过使用“加载”承诺而不是简单地检查是否加载了特定状态来解决避免多次获取的问题。如果您的 api 很慢和/或您的组件可能会在渲染前多次调用 fetch 操作,这将有助于您将其保持在单个 api 调用中。

  // vuex action
  async fetchCustomers({commit, state}) {
    await loadOrWait(loadingPromises, 'customers', async () => {
      // this function is called exactly once
      const response = await api.get('customers');
      commit('SET_CUSTOMERS', {customers : response.data });
    });
    return state.customers;
  },

loaded 只是一个对象,它为每次获取存储一个承诺。我已经看到 someone 使用弱映射(如果内存使用是一个问题,这将是一个不错的选择)

  const loadingPromises = { 
    customers: false,
    customer: { }
  }

loadOrWait 是一个简单的实用程序函数,它要么执行(恰好一次)一个函数(例如,您从 api 中获取)它看到一个获取正在进行中,因此它返回前一个 fetch 调用的承诺。无论哪种情况,您都会得到一个承诺,该承诺将解析为 api 调用的结果。

async function loadOrWait(loadingPromises, id, fetchFunction) {
  if (!loadingPromises[id]) {
    loadingPromises[id] = fetchFunction();
  }
  await loadingPromises[id];
}

也许您希望在获取时更细化,例如,如果尚未获取特定客户,则获取特定客户。

  // vuex action 
  async fetchCustomer({commit, state}, customerId) {
    await loadOrWait(loadingPromises.customer, customerId, async () => {
      const response = await api.get(`customers/${customerId}`);
      commit('SET_CUSTOMER', { customerId, customer: response.data });
    });
    return state.customer[customerId];
  },

其他一些想法:

  • 也许您不想永远缓存结果,在这种情况下,您可以在 loadOrWait 中发挥创意,例如
setTimeout(()=>loadingPromises[id]=false,60*1000)
  • 也许您想定期刷新/轮询数据。让 loadOrWait 保存函数也很简单!当你执行它们时,它会更新状态,如果你是一个优秀的 vue 编码者,你的应用程序将会刷新。
const fetchFunctionMap = {};

async function loadOrWait(loadingPromises, id, fetchFunction) {
  if (!loadingPromises[id]) {
    loadingPromises[id] = fetchFunction();
    // save the fetch for later
    fetchFunctionMap[id] = fn;
  }
  await loadingPromises[id];
}

async function reload(loadingPromises, id) {
  if (fetchFunctionMap[id]){
     loadingPromises[id] = fetchFunctionMap[id]();
  }
  return await loadingPromises[id];
  // strictly speaking there is no need to use async/await in here.
}

//reload the customers every 5 mins
setInterval(()=>reload(loadingPromises,'customers'), 1000 * 60 * 5);