vue.js 2 how to watch store values from vuex

后端 未结 18 2137
傲寒
傲寒 2020-11-27 10:26

I am using vuex and vuejs 2 together.

I am new to vuex, I want to watch a store variable change.

I want t

相关标签:
18条回答
  • 2020-11-27 11:08

    As mentioned above it is not good idea to watch changes directly in store

    But in some very rare cases it may be useful for someone, so i will leave this answer. For others cases, please see @gabriel-robert answer

    You can do this through state.$watch. Add this in your created (or where u need this to be executed) method in component

    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.
        }
    );
    

    More details: https://vuex.vuejs.org/api/#watch

    0 讨论(0)
  • 2020-11-27 11:08

    Create a Local state of your store variable by watching and setting on value changes. Such that the local variable changes for form-input v-model does not directly mutate the store variable.

    data() {
      return {
        localState: null
      };
     },
     computed: {
      ...mapGetters({
        computedGlobalStateVariable: 'state/globalStateVariable'
      })
     },
     watch: {
      computedGlobalStateVariable: 'setLocalState'
     },
     methods: {
      setLocalState(value) {
       this.localState = Object.assign({}, value);
      }
     }
    
    0 讨论(0)
  • 2020-11-27 11:08

    I used this way and it works:

    store.js:

    const state = {
      createSuccess: false
    };
    

    mutations.js

    [mutations.CREATE_SUCCESS](state, payload) {
        state.createSuccess = payload;
    }
    

    actions.js

    async [mutations.STORE]({ commit }, payload) {
      try {
        let result = await axios.post('/api/admin/users', payload);
        commit(mutations.CREATE_SUCCESS, user);
      } catch (err) {
        console.log(err);
      }
    }
    

    getters.js

    isSuccess: state => {
        return state.createSuccess
    }
    

    And in your component where you use state from store:

    watch: {
        isSuccess(value) {
          if (value) {
            this.$notify({
              title: "Success",
              message: "Create user success",
              type: "success"
            });
          }
        }
      }
    

    When user submit form, action STORE will be call, after created success, CREATE_SUCCESS mutation is committed after that. Turn createSuccess is true, and in component, watcher will see value has changed and trigger notification.

    isSuccess should be match with the name you declare in getters.js

    0 讨论(0)
  • 2020-11-27 11:08

    I tried literally everything to get this working.

    Theory

    I found that for some reason, changes to objects from $store don't necessarily trigger a .watch method. My workaround was to

    • Store
      • Create a complex data set which should but doesn't propagate changes to a Component
      • Create an incrementing counter in the state to act as a flag, which does propagate changes to a Component when watched
      • Create a method in $store.mutators to alter the complex dataset and increment the counter flag
    • Component
      • Watch for changes in the $store.state flag. When change is detected, update locally relevant reactive changes from the $store.state complex data set
      • Make changes to the $store.state's dataset using our $store.mutators method

    Implementation

    This is implemented something like this:

    Store

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

    Component

      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);
      }
    

    Runnable demo

    It's convoluted but basically here we are watching for a flag to change and then updating local data to reflect important changes in an object that's stored in the $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>

    0 讨论(0)
  • 2020-11-27 11:09

    When you want to watch on state level, it can be done this way:

    let App = new Vue({
        //...
        store,
        watch: {
            '$store.state.myState': function (newVal) {
                console.log(newVal);
                store.dispatch('handleMyStateChange');
            }
        },
        //...
    });
    
    0 讨论(0)
  • 2020-11-27 11:10

    You should not use component's watchers to listen to state change. I recommend you to use getters functions and then map them inside your component.

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

    In your store:

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

    You should be able to listen to any changes made to your store by using this.myState in your component.

    https://vuex.vuejs.org/en/getters.html#the-mapgetters-helper

    0 讨论(0)
提交回复
热议问题