【问题标题】:Vuex Mapping Getter with Argument - Cached?带有参数的 Vuex 映射 Getter - 缓存?
【发布时间】:2017-11-18 10:37:06
【问题描述】:

这是一个带有 参数化 getter 的 Vuex Store 示例,我需要将其映射到 Vue 实例以在模板中使用。

const store = new Vuex.Store({
  state: {
    lower: 5,
    higher: 10,
    unrelated: 3
  },
  getters: {
    inRange: state => value => {
      console.log('inRange run')
      return (state.lower <= value) && (state.higher >= value)
    }
  },
  mutations: {
    reduceLower: state => state.lower--,
    incrementUnrelated: state => state.unrelated++
  }
})

new Vue({
  el: '#app',
  template: "<div>{{ inRange(4) }}, {{ unrelated }}</div>",
  store,
  computed: Object.assign(
    Vuex.mapGetters(['inRange']),
    Vuex.mapState(['unrelated'])
  ),
})

setTimeout(function() {
  console.log('reduceLower')
  store.commit('reduceLower')
  setTimeout(function() {
    console.log('incrementUnrelated')
    store.commit('incrementUnrelated')
  }, 3000);  
}, 3000);
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.js"></script>
<div id="app"></div>

首先,这看起来确实是有效的工作代码。但是考虑到computed 是一组缓存的计算属性,我很好奇这种情况下的行为,是否正在进行缓存?如果没有,是否需要考虑性能问题?即使该函数不会导致任何状态变化,它应该是method吗?

这是一种反模式吗?该示例不是真实示例,但我确实希望将逻辑集中在商店中。

更新

我更新了示例以说明对 inRange getter 所基于的底层 lower/higher 值的修改对于 Vue 实例确实是反应性的(尽管没有被映射为状态)。我还包含了一个 unrelated 值,它不是计算的一部分,如果映射的 getter 被缓存,修改不相关的值不应该触发 getter 再次被调用,但是确实如此。

我的结论是没有缓存,因此它的性能比传统的计算属性差,但它在功能上仍然是正确的。

关于这种模式是否存在任何缺陷,或者是否存在性能更好的可用模式,这个问题仍然悬而未决。

【问题讨论】:

  • 我还需要用参数缓存我的 getter。我仍然不知道该怎么做。
  • 我已经详细阐述了马特的答案(必须添加另一个答案):stackoverflow.com/a/44607740/1084004

标签: javascript vue.js vuejs2 vuex


【解决方案1】:

在我看来,这是一种反模式。这是一种汇集方法的奇怪方式。另外,不,这里没有缓存,因为 inRange 立即返回一个值(最终函数),而不使用 state 中的任何成员 - 所以 Vue 检测到 0 个反应性依赖项。

Getter 不能以这种方式进行参数化,它们只能派生基于状态的事物。因此,如果范围可以存储在状态中,那将起作用(并且会被缓存)。

这里有类似的问题:vuexjs getter with argument

既然你想集中这个行为 - 我认为你应该在一个单独的模块中做这个,也许作为一个 mixin。这也不会被缓存,因此您必须将它(和输入)包装在组件的 computed 中或使用其他一些记忆

类似这样的:

import { inRange } from './state/methods';
import { mapGetters }  from 'vuex';

const Example = Vue.extend({
  data: {
    rangeInput: 10
  },
  computed: {
    ...mapGetters(['lower', 'higher']),
    inRange() {
      return inRange(this.rangeInput, this.lower, this.higher);
    }
  }
});

【讨论】:

  • 感谢您的输入,您有实现此目的的首选模式示例吗? I think you should just do this in a separate module, perhaps as a mixin. - 我的真实世界版本实际上是在一个模块中,但是作为一个模块是商店的一个细分,模块上下文中的任何解决方案也应该适用于商店。我不赞成使用 mixin 解决方案,因为它具有静态范围,并且在我的解决方案之上没有任何好处。此外,您的链接问题与此问题的关系非常松散,我看不出它的相关性。
  • @mikeapr4 查看更新的答案以获取示例。 state/methods.js 文件将是存储逻辑的集中位置。据我所知,Vuex 没有提供公开普通方法的方法,所以我认为你必须使用 Vuex 范式之外的模块。至于链接的问题,它只是更深入地探讨了为什么 getter 不能接收状态本身以外的输入。
  • @mikeapr4 混合计算值(和方法)可以像任何组件一样访问存储。我不确定静态范围是什么意思。
  • 我不确定是否将此方法称为反模式。它甚至在官方 vuex 文档中:vuex.vuejs.org/guide/getters.html#method-style-access
  • @Deleroy 如前所述,这只是一种意见。我认为在大多数情况下丢失缓存是没有意义的。 YMMV
【解决方案2】:

为了说明为什么我接受了马特的answer,这里是一个有效的sn-p,要注意的关键点是:

Vuex.mapGetters(['inRange'])

有一个 true 计算属性:

inRange4: function() {
  return this.$store.getters.inRange(4);
}

从运行 sn-p 可以看出,这导致值被正确缓存。正如我所说,这种模式不是我可以使用的模式,因为我最终会得到太多的计算属性(inRange1、inRange2、inRange3 等),但它确实通过相关示例回答了问题。

我已选择继续使用问题中的代码,未更改

注意:Matt 的回答与这段代码不完全匹配,我相信他的意图是将来自 store 的状态映射到 Vue 实例,我认为这是不必要的。

const store = new Vuex.Store({
  state: {
    lower: 5,
    higher: 10,
    unrelated: 3
  },
  getters: {
    inRange: state => value => {
      console.log('inRange run')
      return (state.lower <= value) && (state.higher >= value)
    }
  },
  mutations: {
    reduceLower: state => state.lower--,
    incrementUnrelated: state => state.unrelated++
  }
})

new Vue({
  el: '#app',
  template: "<div>{{ inRange4 }}, {{ unrelated }}</div>",
  store,
  computed: Object.assign(
    {
      inRange4: function() {
        return this.$store.getters.inRange(4);
      }
    },
    Vuex.mapState(['unrelated'])
  ),
})

setTimeout(function() {
  console.log('reduceLower')
  store.commit('reduceLower')
  setTimeout(function() {
    console.log('incrementUnrelated')
    store.commit('incrementUnrelated')
  }, 3000);  
}, 3000);
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.js"></script>
<div id="app"></div>

【讨论】:

    【解决方案3】:

    似乎有办法解决这个问题,使用计算值创建一个地图并访问它

    inrange[4];
    

    我经常使用它来初始化不同类型的访问器,我从后端获取一个数组,并且需要通过某些字段(例如 ID)来访问它。 对于上面的例子,由于范围很小,这似乎是合理的:

    const store = new Vuex.Store({
      state: {
        lower: 5,
        higher: 10,
        unrelated: 3
      },
      getters: {
        inRange: state => {
          console.log('inRange run')
          var result = {};
          for( var i = state.lower; i < state.higher; i++) {
            result[i] = true;
          }
          return result;
        }
      },
      mutations: {
        reduceLower: state => state.lower--,
        incrementUnrelated: state => state.unrelated++
      }
    })
    
    new Vue({
      el: '#app',
      template: "<div>{{ inRange[4] }}, {{ unrelated }}</div>",
      store,
      computed: Object.assign(
        {
          inRange: function() {
            return this.$store.getters.inRange;
          }
        },
        Vuex.mapState(['unrelated'])
      ),
    })
    
    setTimeout(function() {
      console.log('reduceLower')
      store.commit('reduceLower')
      setTimeout(function() {
        console.log('incrementUnrelated')
        store.commit('incrementUnrelated')
      }, 3000);  
    }, 3000);
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex/dist/vuex.js"></script>
    <div id="app"></div>

    【讨论】:

      猜你喜欢
      • 2018-05-07
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 2018-04-20
      • 2021-07-19
      • 1970-01-01
      • 2020-12-18
      • 2021-01-18
      相关资源
      最近更新 更多