如何使用Vuex / Vue处理异步数据检索

时间:2018-09-21 07:37:50

标签: javascript vue.js vuejs2 vuex

我有一个具有通用堆栈的简单应用程序:

  • 后端服务器(Rails)
  • 前端应用(Vue)
  • 数据库(PG)

Vue应用程序使用Vuex存储库的操作从后端获取数据:

// store/store.js
import Vue from 'vue';
import Vuex from 'vuex';
import * as MutationTypes from '@/store/mutation-types';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    investment: {},
  },
  mutations: {
    [MutationTypes.SET_INVESTMENT_SHOW](state, investment) {
      state.investment = investment;
    },
  },
  actions: {
    fetchInvestment({ commit }, id) {
      InvestmentsApi.get(id).then((response) => {
        commit(MutationTypes.SET_INVESTMENT_SHOW, response.data);
      });
    },
  },
  getters: {
    participation: state =>
      state.investment.included[0],
  },
});

在组件的生命周期挂钩中调用该操作,如下所示:

// components/Investment.vue

import { mapActions, mapGetters } from 'vuex';
export default {
  name: 'Investment',
  computed: {
    ...mapState(['investment']),
    ...mapGetters(['participation']),
  },
  created() {
    this.fetchData(this.$route.params.id);
  },
  methods: mapActions({
    fetchData: 'fetchInvestment',
  }),
};

我上面编写的代码有问题,实际上我在模板中使用了计算值“参与”:

<BaseTitleGroup
  :subtitle="participation.attributes.name"
  title="Investissements"
/>

当然,因为我在组件呈现自身时使用了参与性,所以我会从getter方法获得此错误:

Error in render: "TypeError: Cannot read property '0' of undefined"

found in

---> <InvestmentSummary> at src/views/InvestmentSummary.vue
       <App> at src/App.vue
         <Root>

我认为有多种解决方案可以解决此问题,我想知道哪种方法是最佳实践,或者是否有更好的方法。

  1. 第一个解决方案是在模板中放置v-if属性,以防止元素在等待数据时呈现
    • Con:渲染偏移量(元素在数据存在时开始渲染)?
    • Con:我必须对应用程序中处理异步数据的每个组件都执行此操作,直觉上,我宁愿在其他地方(也许是商店?)进行处理。
  2. 渲染元素并将假数据放入存储中,例如“正在加载...”
    • 缺点:当文本从加载切换为真实文本时,用户在加载页面时看到的小故障很丑。
    • 缺点:我商店的空版本写起来会很痛苦,而且当我的应用程序扩展时会变得非常大
  3. 更改getter以返回初始空数据,而不是存储
    • 骗局:吸气剂变得更加复杂
    • Con:不需要吸气剂的数据(也许可以直接从州访问)
  4. 还有什么?

我正在寻找最好的解决方案来处理这种模式,即使它是上述之一,但我不确定哪个是最好的。非常感谢您的阅读!另外,我使用vue框架,但我认为这更多是关于异步数据和呈现的现代javascript框架处理的一般问题。

很抱歉,很长的帖子,这是土豆! (糟糕,不是在9gag上;))

2 个答案:

答案 0 :(得分:1)

我认为没有最好的解决方案,只需选择一种并在各处使用它,而不要混合使用。

v-if可能更好,以防您要从嵌套属性渲染数据-v-if="object.some.nested.property v-for="el in object.some.nested.property"可以工作,但预定义object = {}则不能(它会抛出错误,{ {1}}未定义,您正在尝试访问它。

我不会像您的示例中那样放置任何假数据,但是您可以使用ES6 Classes定义默认对象并将其设置为默认值。只要您的类对象具有适当的结构(并且它在语法上也是透明的并且易于理解),这也可以解决上述预定义问题。

关于第三个选项-将空数据提供给getter不必太复杂-只需将getter更改为:

some

如果已定义,则使用getters: { participation: state => state.investment.included[0] || new DefaultParticipationObject() // I don't know what's in included array }, ,否则使用默认对象。

答案 1 :(得分:1)

在Angular中,有Elvis(安全导航)运算符,这是处理 最终 到达的反应性数据的简洁方法。

如果在Vue模板编译器中可用,则您的模板将如下所示:

<BaseTitleGroup
  :subtitle="participation?.attributes?.name"
  title="Investissements"
/>

但是,Evan You说it sounds like a code smell

  

您的模型/状态应该尽可能地可预测。

尝试将注释扩展到您的上下文中,我认为这意味着 您的模板比您的商店更了解您的数据结构

模板

"participation.attributes.name"

等于:

state.investment.included[0].attributes.name

商店

state: {
  investment: {},
},

由于吸气剂在那里可以为组件(及其模板)提供服务,所以我会选择增强吸气剂。

getters: {
  participation_name: state => {
    return 
      (state.investment.included 
       && state.investment.included.length
       && state.investment[0]
       && state.investment[0].attributes
       && state.investment[0].attributes.name)
      || null;
},

<BaseTitleGroup
  :subtitle="participation_name"
  title="Investissements"
/>

但是,如果您想要Elvis功能,则可以在mixin中提供它。

var myMixin = {
  computed: {
    elvis: {
      get: function() {
        return (known, unknown) => {
          // use regex split to handle both properties and array indexing
          const paths = unknown.split(/[\.(\[.+\])]+/); 
          let ref = known
          paths.forEach(path => { if (ref) ref = ref[path] });
          return ref;
        }
      }
    },
  }
}

export default {
  name: 'Investment',
  ...
  mixins: [myMixin],
  computed: {
    ...mapState(['investment']),
    participation_name() {
      return this.elvis(this.investment, 'included[0].attributes.name')
    }
  },
  ...
};