如何从vuex中查看存储的值?

309

我正在同时使用vuexvuejs 2

我对vuex还不太熟悉,我想监视一个store变量的更改。

我想在我的vue组件中添加watch函数。

目前为止,这就是我所拥有的。

import Vue from 'vue';
import {
  MY_STATE,
} from './../../mutation-types';

export default {
  [MY_STATE](state, token) {
    state.my_state = token;
  },
};

我想知道 my_state 是否有任何更改。

在我的 Vue.js 组件中,如何监视 store.my_state

22个回答

352

比方说,你有一个水果篮子,每当你往里添加或移除一个水果时,你想要(1)显示有关水果数量的信息,但是你还想以一些花哨的方式(2)被通知水果数量的计数...

fruit-count-component.vue

<template>
  <!-- We meet our first objective (1) by simply -->
  <!-- binding to the count property. -->
  <p>Fruits: {{ count }}</p>
</template>

<script>
import basket from '../resources/fruit-basket'

export default () {
  computed: {
    count () {
      return basket.state.fruits.length
      // Or return basket.getters.fruitsCount
      // (depends on your design decisions).
    }
  },
  watch: {
    count (newCount, oldCount) {
      // Our fancy notification (2).
      console.log(`We have ${newCount} fruits now, yay!`)
    }
  }
}
</script>
请注意,watch对象中的函数名必须与computed对象中的函数名匹配。在上面的示例中名称为count。 监视的属性的新旧值将作为参数传递到监视回调函数(即count函数)中。 篮子商店可以看起来像这样: fruit-basket.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const basket = new Vuex.Store({
  state: {
    fruits: []
  },
  getters: {
    fruitsCount (state) {
      return state.fruits.length
    }
  }
  // Obviously you would need some mutations and actions,
  // but to make example cleaner I'll skip this part.
})

export default basket

您可以在以下资源中阅读更多信息:


我在想当watch操作需要分成两步时该怎么办:1)首先,检查所需数据是否已缓存,如果是,则返回缓存的数据;2)如果缓存失败,我需要一个异步ajax操作来获取数据,但这似乎是action的工作。希望我的问题有意义,谢谢! - 1Cr18Ni9
2
相比于Micah5的答案,这样做有什么好处呢?它需要维护的代码更少。 - Exocentric
1
@Exocentric 当我回答时,问题对我来说并不清楚。没有上下文说明为什么需要观察属性。可以将其视为:“我想要观察变量X,以便我可以做Y。”这可能是为什么大多数答案提出了如此不同的方法。没有人知道意图是什么。这就是为什么我在我的答案中包含了“目标”。如果您有不同的目标,不同的答案可能更适合您。我的示例只是实验的起点。它不是一个即插即用的解决方案。没有“好处”,因为好处取决于您的情况。 - Anastazy
@1Cr18Ni9 我认为缓存不应该放在组件代码中。你最终会过度设计本应非常简单的事情(获取数据并将其绑定到视图)。浏览器已经实现了缓存。您可以通过从服务器发送正确的标头来利用它。这里有一个简单的解释:https://csswizardry.com/2019/03/cache-control-for-civilians/。您还可以查看ServiceWorkers,它允许网站即使没有互联网连接也能工作。 - Anastazy
为什么你必须要观察计算出来的值?为什么计算出来的值不能自动创建一个可用的观察器呢?毕竟,这就是计算属性的目的。 - geoidesic

269

就像这样简单:

watch: {
  '$store.state.drawer': function() {
    console.log(this.$store.state.drawer)
  }
}

如果该存储区位于一个模块中,请使用:

'$store.state.myModule.drawer'

对于嵌套的文件,请使用:

'$store.state.fileOne.fileTwo.myModule.drawer'

21
这比这里的任何答案都简单得多...有反对这样做的论点吗..? - Inigo
131
太简单了,看起来不像 JavaScript,JavaScript 必须更加复杂。 - digout
5
非常酷。我也对这种方法感兴趣,但希望知道它的任何不足之处。到目前为止,它似乎运作良好。 - namero999
3
说真的,这种方法似乎比被接受的答案更好,因为它不需要在 watch 和 computed 中重复函数名称。请专家评论一下为什么要或者不要这样做? - Exocentric
17
顺便提一句,如果该存储区使用了模块化命名空间,只需写'$store.state.module.something' - xji
显示剩余10条评论

75

您不应使用组件的观察器来监听状态变化。我建议您使用getter函数,然后将它们映射到组件内部。

import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters({
      myState: 'getMyState'
    })
  }
}

在您的商店中:

const getters = {
  getMyState: state => state.my_state
}

使用this.myState在您的组件中,您应该能够听取对您的存储所做的任何更改。

https://vuex.vuejs.org/zh/guide/getters.html#mapgetters-%E8%BE%85%E5%8A%A9%E5%87%BD%E6%95%B0


2
我不知道如何实现mapGetters。你能给我一个例子吗?这将会非常有帮助。目前我只是按照GONG的答案来实现。谢谢。 - Rbex
2
@Rbex,“mapGetters”是“vuex”库的一部分。您不需要自己实现它。 - Gabriel Robert
137
这个答案是错误的。实际上,他需要观察计算属性。 - chesscov77
19
调用getter只能获取该时刻的状态。如果您希望该属性反映来自另一个组件的状态更改,则必须监视它。 - C Tierney
5
为什么“不应使用组件的观察器来监听状态改变”? 这里有一个你可能没有想到的例子,如果我想要观察来自状态的令牌,并在其更改时重定向到另一页。因此,在某些情况下,您需要这样做。也许您需要更多的经验才能知道何时使用它。 - Shlomi Levi
显示剩余4条评论

59

如上所述,在商店里直接观察变化不是一个好主意

但在一些极为罕见的情况下,如果有人觉得有用,我将留下这个答案。对于其他情况,请参阅 @gabriel-robert 的回答

您可以通过 state.$watch 来实现这一点。将其添加到您的组件中的created方法(或者需要执行此操作的位置)中。

this.$store.watch(
    function (state) {
        return state.my_state;
    },
    function () {
        //do something on data change
    },
    {
        deep: true //add this if u need to watch object properties change etc.
    }
);

更多细节: https://vuex.vuejs.org/api/#watch


3
我认为直接观察状态并不是一个好主意,我们应该使用getters。请参考此链接了解有关“mapGetters”助手的更多信息:https://vuex.vuejs.org/zh/guide/getters.html#%E4%BD%BF%E7%94%A8-mapgetters-%E5%8A%A9%E6%89%8B - Gabriel Robert
15
@GabrielRobert 我认为两种方法都有各自的应用场景。如果你需要根据某些条件进行响应式模板更改,使用mapState与计算属性结合是比较合理的。但如果你需要在组件中控制流程,那么就需要使用完整的watch方法。你是对的,不应该仅仅使用普通的组件watcher,而是采用state.$watch方法来满足这些需求。 - roberto tomás
20
大家都提到这个,但没有人说原因!我试图构建一个vuex存储,它在更改时自动与数据库同步。我觉得在存储上使用watcher是最无摩擦的方式!你认为呢?还是不太好的想法? - mesqueeb

22

我认为提问者希望在Vuex中使用watch。

this.$store.watch(
      (state)=>{
        return this.$store.getters.your_getter
      },
      (val)=>{
       //something changed do something

      },
      {
        deep:true
      }
      );

1
这个应该在哪里调用? - user12009061
2
在某个地方通过this访问Vue实例,比如created钩子函数。基本上,任何你需要的组件都可以这样做。 - vhoyer

19

这是为那些无法通过getters解决问题,实际上确实需要一个watcher的人准备的,例如与非Vue第三方库进行交互(请参见 Vue Watchers 了解何时使用watchers)。

Vue组件的watchers和计算属性都可以在计算属性上工作。因此,在vuex中也没有任何不同:

import { mapState } from 'vuex';

export default {
    computed: {
        ...mapState(['somestate']),
        someComputedLocalState() {
            // is triggered whenever the store state changes
            return this.somestate + ' works too';
        }
    },
    watch: {
        somestate(val, oldVal) {
            // is triggered whenever the store state changes
            console.log('do stuff', val, oldVal);
        }
    }
}

如果只涉及到合并本地和全局状态,mapState的文档也提供了一个示例:

computed: {
    ...mapState({
        // to access local state with `this`, a normal function must be used
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
    }
})

不错的技巧,但是你觉得这太繁琐了吧? - MartianMartian
2
如果文档中有说明,那么它就不是黑客行为,对于Vue/Vuex来说也不是专业的论据。 - dube

14

我尝试了所有可能的方法让它工作。

理论

我发现,对于来自$store的对象的更改不一定会触发.watch方法。我的解决方法是

  • 存储
    • 创建一个复杂的数据集,应该但不会将更改传播到组件
    • state中创建一个递增计数器作为标志,当被监视时,它会传播到组件
    • $store.mutators中创建一个方法以修改复杂的数据集并增加计数器标志
  • 组件
    • 监视$store.state标志的更改。当检测到更改时,从$store.state复杂的数据集更新本地相关的反应式更改
    • 使用我们的$store.mutators方法更改$store.state的数据集

实现

实现类似于以下内容:

存储

let store = Vuex.Store({
  state: {
    counter: 0,
    data: { someKey: 0 }
  },
  mutations: {
    updateSomeKey(state, value) {
      update the state.data.someKey = value;
      state.counter++;
    }
  }
});

组件

  data: {
    dataFromStoreDataSomeKey: null,
    someLocalValue: 1
  },
  watch: {
    '$store.state.counter': {
        immediate: true,
        handler() {
           // update locally relevant data
           this.someLocalValue = this.$store.state.data.someKey;
        }
     }
  },
  methods: {
    updateSomeKeyInStore() { 
       this.$store.commit('updateSomeKey', someLocalValue);
  }

可运行演示

虽然有些复杂,但基本上我们正在观察标志位的变化,然后更新本地数据以反映存储在$state中的对象中的重要更改。

Vue.config.devtools = false

const store = new Vuex.Store({
  state: {
    voteCounter: 0,
    // changes to objectData trigger a watch when keys are added,
    // but not when values are modified?
    votes: {
      'people': 0,
      'companies': 0,
      'total': 0,
    },
  },
  mutations: {
    vote(state, position) {
      state.votes[position]++;
      state.voteCounter++;
    }
  },
});


app = new Vue({
  el: '#app',
  store: store,
  data: {
    votesForPeople: null,
    votesForCompanies: null,
    pendingVote: null,
  },
  computed: {
    totalVotes() {
      return this.votesForPeople + this.votesForCompanies
    },
    peoplePercent() {
      if (this.totalVotes > 0) {
        return 100 * this.votesForPeople / this.totalVotes
      } else {
        return 0
      }
    },
    companiesPercent() {
      if (this.totalVotes > 0) {
        return 100 * this.votesForCompanies / this.totalVotes
      } else {
        return 0
      }
    },
  },
  watch: {
    '$store.state.voteCounter': {
        immediate: true,
        handler() {
          // clone relevant data locally
          this.votesForPeople = this.$store.state.votes.people
          this.votesForCompanies = this.$store.state.votes.companies
        }
     }
  },
  methods: {
    vote(event) {
      if (this.pendingVote) {
        this.$store.commit('vote', this.pendingVote)
      }
    }
  }
  
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
<script src="https://unpkg.com/vuex@3.5.1/dist/vuex.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">


<div id="app">
   <form @submit.prevent="vote($event)">
      <div class="form-check">
         <input
           class="form-check-input" 
           type="radio" 
           name="vote" 
           id="voteCorps"
           value="companies"
           v-model="pendingVote"
          >
         <label class="form-check-label" for="voteCorps">
         Equal rights for companies
         </label>
      </div>
      <div class="form-check">
         <input
           class="form-check-input" 
           type="radio" 
           name="vote"
           id="votePeople" 
           value="people"
           v-model="pendingVote"
         >
         <label class="form-check-label" for="votePeople">
         Equal rights for people
         </label>
      </div>
      <button
        class="btn btn-primary"
        :disabled="pendingVote==null"
      >Vote</button>
   </form>
   <div
     class="progress mt-2"
     v-if="totalVotes > 0"
    >
      <div class="progress-bar"
        role="progressbar"
        aria-valuemin="0"
        :style="'width: ' + peoplePercent + '%'"
        :aria-aluenow="votesForPeople"
        :aria-valuemax="totalVotes"
      >People</div>
      <div
        class="progress-bar bg-success"
        role="progressbar"
        aria-valuemin="0"
        :style="'width: ' + companiesPercent + '%'"
        :aria-valuenow="votesForCompanies"
        :aria-valuemax="totalVotes"
      >Companies</div>
   </div>
</div>


1
你遇到了Vue的响应性警告,这是相当好记录的:一旦被观察到,就不能再向根数据对象添加响应式属性。因此建议在创建实例之前先声明所有根级响应式属性。 - Derek Pollard
1
有关此问题的更多信息以及如何解决它,请参阅:https://vuejs.org/v2/guide/reactivity.html#For-Objects - Derek Pollard
1
这里的答案是在你的mutations中使用Vue.set(state.votes, newVotesObject) - Derek Pollard
但是公司也是由人组成的!:-P - Adonis Gaitatzis
@DerekPollard Vue.set()并不总是有用的,例如当您将非响应式数据导入到您的Vuex状态或Vue组件中时。 - Adonis Gaitatzis
显示剩余6条评论

13

如果您使用 TypeScript,那么您可以:

import { Watch } from "vue-property-decorator";

..

@Watch("$store.state.something")
private watchSomething() {
   // use this.$store.state.something for access
   ...
}


为什么会被踩?只是因为这个解决方案是针对vue-class-component的,而TO正在寻求旧的vue-class样式吗?我认为前者更可取。也许@Zhang Sol可以在介绍中明确说明,这是专门针对vue-class-component的解决方案? - JackLeEmmerdeur
不确定为什么一个 TypeScript 装饰器会比这个简单的 Vue 原生解决方案更可取:https://dev59.com/algQ5IYBdhLWcg3wIAWb#56461539 - yann_yinn
@yann_yinn,因为你的示例在 TypeScript 组件中无法工作。 - Desprit
@Desprit 是的,但问题并没有使用也没有提到TypeScript。但对于TypeScript用户来说,这仍然是一个有用的提示。 - yann_yinn

13

如果你只是想要监听一个状态属性,并且根据该属性的更改在组件中执行相应操作,则请参见下面的示例。

store.js中:

export const state = () => ({
 isClosed: false
})
export const mutations = {
 closeWindow(state, payload) {
  state.isClosed = payload
 }
}

在这个场景中,我正在创建一个 boolean 状态属性,我将在应用程序中的不同位置进行更改,如下所示:

this.$store.commit('closeWindow', true)

现在,如果我需要在另一个组件中监视该状态属性,然后更改本地属性,我将在 mounted 钩子中编写以下内容:

现在,如果我需要在其他组件中观察该状态属性并改变本地属性,我会在mounted钩子中编写以下代码:
mounted() {
 this.$store.watch(
  state => state.isClosed,
  (value) => {
   if (value) { this.localProperty = 'edit' }
  }
 )
}
首先,我正在状态属性上设置一个监听器,然后在回调函数中,我使用该属性的value来更改localProperty。 希望这可以帮助!

7

通过监听值的变化并进行设置,创建您的存储变量的本地状态(Local state),以使表单输入v-model的局部变量不会直接改变存储变量(store variable)

data() {
  return {
    localState: null
  };
 },
 computed: {
  ...mapGetters({
    computedGlobalStateVariable: 'state/globalStateVariable'
  })
 },
 watch: {
  computedGlobalStateVariable: 'setLocalState'
 },
 methods: {
  setLocalState(value) {
   this.localState = Object.assign({}, value);
  }
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,