vue.js 2 jak obserwować przechowywanie wartości z vuex

168

Używam vuexi vuejs 2razem.

Jestem nowy vuex, chcę obserwować storezmianę zmiennej.

Chcę dodać watchfunkcję w moimvue component

Oto, co mam do tej pory:

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

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

Chcę wiedzieć, czy są jakieś zmiany w my_state

Jak oglądać store.my_statew moim komponencie vuejs?

raffffffff
źródło
użyj wtyczki Vuejs, która jest dostarczana z chromem, przyda się
AKASH PANDEY

Odpowiedzi:

206

Załóżmy na przykład, że masz kosz owoców i za każdym razem, gdy dodajesz lub usuwasz owoc z koszyka, chcesz (1) wyświetlić informacje o liczbie owoców, ale także (2) chcesz być powiadamiany liczenie owoców w jakiś fantazyjny sposób ...

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>

Zwróć uwagę, że nazwa funkcji w watchobiekcie musi odpowiadać nazwie funkcji w computedobiekcie. W powyższym przykładzie nazwa tocount .

Nowe i stare wartości obserwowanej właściwości zostaną przekazane jako parametry do funkcji watch callback (funkcja count).

Sklep z koszykami mógłby wyglądać tak:

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

Możesz przeczytać więcej w następujących zasobach:

Anastazy
źródło
Zastanawiam się tylko, co watchpowinienem zrobić, gdy akcja powinna zostać podzielona na dwa etapy: 1) Po pierwsze, sprawdzenie, czy pożądane dane są buforowane i czy po prostu zwracają dane z pamięci podręcznej; 2) Jeśli pamięć podręczna nie powiodła się, potrzebuję akcji AJAX async, aby pobrać dane, ale wydaje się, że to actionjest praca. Mam nadzieję, że moje pytanie będzie miało sens, dziękuję!
1Cr18Ni9
Jaka jest korzyść z tego, w porównaniu z odpowiedzią micah5, która po prostu ustawia obserwatora w komponencie, na wartość sklepu? Ma mniej kodu do utrzymania.
Exocentric
@Exocentric Kiedy pisałem odpowiedź, pytanie nie było dla mnie jasne. Nie ma kontekstu, dla którego warto obserwować właściwości. Pomyśl o tym w ten sposób: „Chcę oglądać zmienną X, więc mogę zrobić Y”. Prawdopodobnie dlatego większość odpowiedzi proponuje tak bardzo różne podejścia. Nikt nie wie, jaki jest zamiar. Dlatego w mojej odpowiedzi zawarłem „cele”. Jeśli masz różne cele, inna odpowiedź może do nich pasować. Mój przykład jest tylko punktem wyjścia do eksperymentów. Nie jest to rozwiązanie typu plug & play. Nie ma żadnej „korzyści”, ponieważ zależy ona od Twojej sytuacji.
Anastazy
@ 1Cr18Ni9 Myślę, że buforowanie nie należy do kodu komponentu. Skończysz z nadmierną inżynierią czegoś, co powinno być naprawdę proste (pobieranie danych i wiązanie ich z widokiem). Buforowanie jest już zaimplementowane w przeglądarce. Możesz to wykorzystać wysyłając poprawne nagłówki z serwera. Proste wyjaśnienie tutaj: csswizardry.com/2019/03/cache-control-for-civilians . Możesz również przyjrzeć się ServiceWorkers, które pozwalają na działanie strony internetowej nawet bez połączenia z Internetem.
Anastazy
61

Nie należy używać obserwatorów składnika do nasłuchiwania zmiany stanu. Zalecam użycie funkcji getters, a następnie zmapowanie ich wewnątrz komponentu.

import { mapGetters } from 'vuex'

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

W Twoim sklepie:

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

Powinieneś być w stanie wysłuchać wszelkich zmian wprowadzonych w Twoim sklepie za pomocą this.myState komponentu.

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

Gabriel Robert
źródło
1
Nie wiem, jak zaimplementować mapGetters. Czy możesz wskazać mi przykład. To byłaby duża pomoc. Właśnie wdrażam odpowiedź GONG w tej chwili. TY
Rbex
1
@Rbex "mapGetters" jest częścią biblioteki „vuex”. Nie musisz tego wdrażać.
Gabriel Robert
69
Ta odpowiedź jest po prostu błędna. W rzeczywistości musi obserwować obliczone właściwości.
Juan
15
Raz wywołany obiekt pobierający pobierze tylko stan w danym momencie. Jeśli chcesz, aby ta właściwość odzwierciedlała zmianę stanu z innego komponentu, musisz to obserwować.
C Tierney
3
Dlaczego „Nie należy używać obserwatorów komponentów do nasłuchiwania zmiany stanu”? Oto przykład, o którym możesz nie pomyśleć, jeśli chcę oglądać na token ze stanu, a kiedy zmienia się, aby przekierować na inną stronę. więc jest kilka przypadków, w których musisz to zrobić. może potrzebujesz więcej doświadczenia, żeby to wiedzieć.
Shlomi Levi
46

To tak proste, jak:

watch: {
  '$store.state.drawer': function() {
    console.log(this.$store.state.drawer)
  }
}
micah5
źródło
6
To jest cholernie prostszy widok niż którakolwiek z odpowiedzi tutaj… czy jest jakiś argument przeciwko temu…?
Inigo,
19
Jest zbyt proste, więc nie wygląda jak js, js musi być bardziej skomplikowane.
Digout
1
Byłoby jeszcze prostsze, gdyby tak byłofunction(n) { console.log(n); }
WofWca
2
Super fajne. Zainteresowany także jakąkolwiek wadą tego podejścia. Jak dotąd wydaje się, że działa dobrze.
namero999
1
Poważnie. Wydaje się to znacznie lepsze niż zaakceptowana odpowiedź, która wymaga zduplikowanych nazw funkcji w zegarku i obliczeniach. Czy ekspert może skomentować, dlaczego lub dlaczego nie zrobić tego w ten sposób?
Exocentric
42

Jak wspomniano powyżej, nie jest dobrym pomysłem obserwowanie zmian bezpośrednio w sklepie

Ale w bardzo rzadkich przypadkach może to być dla kogoś przydatne, więc zostawię tę odpowiedź. W innych przypadkach zobacz odpowiedź @ gabriel-robert

Możesz to zrobić przez state.$watch. Dodaj to do swojej created(lub tam, gdzie potrzebujesz tego do wykonania) metody w komponencie

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

Więcej szczegółów: https://vuex.vuejs.org/api/#watch

GONG
źródło
3
Uważam, że bezpośrednie oglądanie stanu nie jest dobrym pomysłem. Powinniśmy używać getterów. vuex.vuejs.org/en/getters.html#the-mapgetters-helper
Gabriel Robert
14
@GabrielRobert Myślę, że jest miejsce dla obu. Jeśli chcesz reaktywnie zmienić warunki szablonu na podstawie, użycie wartości obliczonej z mapState itp. Ma sens. Ale poza tym, tak jak w przypadku równomiernej kontroli przepływu w komponencie, potrzebujesz pełnego zegarka. Masz rację, nie powinieneś używać zwykłych obserwatorów komponentów, ale stan. $ Watch jest przeznaczony do takich przypadków użycia
roberto tomás
14
Wszyscy o tym wspominają, ale nikt nie mówi dlaczego! Próbuję zbudować sklep Vuex, który jest automatycznie synchronizowany z bazą danych po zmianach. Czuję, że obserwatorzy w sklepie to najbardziej bezproblemowy sposób! Co myślisz? Wciąż nie jest to dobry pomysł?
mesqueeb
16

Myślę, że pytający chce używać zegarka z Vuex.

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

      },
      {
        deep:true
      }
      );
yeahdixon
źródło
13

To jest dla wszystkich ludzi, którzy nie mogą rozwiązać swojego problemu z getterami i naprawdę potrzebują obserwatora, np. Aby porozmawiać z rzeczami stron trzecich niezwiązanymi z vue (patrz Vue Watchers kiedy używać obserwatorów).

Obserwatory i obliczone wartości komponentu Vue również działają na obliczonych wartościach. Więc nie inaczej jest z 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);
        }
    }
}

jeśli chodzi tylko o połączenie stanu lokalnego i globalnego, dokument mapState zawiera również przykład:

computed: {
    ...mapState({
        // to access local state with `this`, a normal function must be used
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
    }
})
dube
źródło
niezły hack, ale po prostu zbyt nużący, nie sądzisz?
Martian2049,
2
To nie jest hack, jeśli jest w dokumentacji, prawda? Ale wtedy, to nie jest argument za pro-vue / vuex albo
Dube
8

jeśli używasz maszynopisu, możesz:

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

..

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

Zhang Sol
źródło
Dlaczego dokładnie to zostało odrzucone? Tylko dlatego, że rozwiązanie dotyczy komponentu klasy vue, a TO pytał o stare style klas vue? Preferuję ten pierwszy. Może @Zhang Sol mógłby wspomnieć we wstępie, że jest to jawnie dla komponentu klasy vue?
JackLeEmmerdeur,
Zwróć uwagę, dlaczego dekorator maszynopisu byłby lepszy od natywnego rozwiązania vue tak prostego jak to: stackoverflow.com/a/56461539/3652783
yann_yinn
6

Utwórz lokalny stan zmiennej sklepu, obserwując i ustawiając zmiany wartości. Taka, że ​​zmienna lokalna zmienia się dla modelu v-wejściowego formularza, nie powoduje bezpośredniej mutacji zmiennej magazynu .

data() {
  return {
    localState: null
  };
 },
 computed: {
  ...mapGetters({
    computedGlobalStateVariable: 'state/globalStateVariable'
  })
 },
 watch: {
  computedGlobalStateVariable: 'setLocalState'
 },
 methods: {
  setLocalState(value) {
   this.localState = Object.assign({}, value);
  }
 }
Mukundhan
źródło
5

Najlepszym sposobem obserwowania zmian w sklepie jest użycie mapGetterstak, jak powiedział Gabriel. Ale jest taki przypadek, kiedy nie da się tego zrobić mapGettersnp. Chcesz coś pobrać ze sklepu za pomocą parametru:

getters: {
  getTodoById: (state, getters) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}

w takim przypadku nie możesz użyć mapGetters. Zamiast tego możesz spróbować zrobić coś takiego:

computed: {
    todoById() {
        return this.$store.getters.getTodoById(this.id)
    }
}

Ale niestety todoById zostanie zaktualizowany tylko wtedy, gdy this.idzostanie zmieniony

Jeśli chcesz zaktualizować komponenty, skorzystaj z this.$store.watch rozwiązania dostarczonego przez Gong . Lub zarządzaj komponentem świadomie i aktualizuj, this.idgdy zajdzie potrzeba todoById.

Arseniy-II
źródło
Dziękuję Ci. To jest dokładnie mój przypadek użycia i rzeczywiście gettera nie można wtedy obejrzeć ...
pscheit
5

Jeśli chcesz po prostu oglądać do własności państwowej , a następnie działać w ramach komponentu odpowiednio do zmian tej nieruchomości następnie patrz przykład poniżej.

W store.js:

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

W tym scenariuszu tworzę booleanwłaściwość stanu, którą zamierzam zmienić w różnych miejscach aplikacji, tak jak poniżej:

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

Teraz, jeśli muszę obserwować tę właściwość stanu w innym komponencie, a następnie zmienić właściwość lokalną, napisałbym w mounted haku:

mounted() {
 this.$store.watch(
  state => state.isClosed,
  (value) => {
   if (value) { this.localProperty = 'edit' }
  }
 )
}

Najpierw ustawiam obserwatora na właściwości stanu, a następnie w funkcji wywołania zwrotnego używam value właściwości tej właściwości, aby zmienić localProperty.

Mam nadzieję, że to pomoże!

Jakub A Suplicki
źródło
3

Jeśli chcesz oglądać na poziomie stanu, możesz to zrobić w następujący sposób:

let App = new Vue({
    //...
    store,
    watch: {
        '$store.state.myState': function (newVal) {
            console.log(newVal);
            store.dispatch('handleMyStateChange');
        }
    },
    //...
});
Andy
źródło
Nie jest dobrym pomysłem obsługiwanie store.statezmian przez dispatchdziałanie stanu ze składnika, ponieważ to zachowanie działa tylko wtedy, gdy używasz tego składnika. Możesz również zakończyć nieskończoną pętlą. Zegarek store.statezmienić rzadko korzystają, na przykład, jeśli masz komponent lub stronę, która powinna zrobić jakąś akcję w oparciu o store.statezmieniły, że nie mógł obsługiwane przez korzystając obliczane mapState tylko tam, gdzie nie można porównać newValuevsoldValue
Januartha
@Januartha, jaka jest więc twoja sugestia odnośnie tego problemu?
Billal Begueradj
@Andy tak, oczywiście, że działa. Chcę tylko zauważyć, dlaczego dzwonisz store.dispatch? jeśli chcesz obsłużyć store.statezmianę dla store' why not handle it inside store.mutations`?
Januartha
@BillalBEGUERADJ I prever Dube rozwiązanie jest odkurzacz
Januartha
@Januartha, ponieważ przed wykonaniem mutacji może wystąpić wywołanie Ajax, dlatego store.dispatchnajpierw używam . Na przykład chcę pobrać wszystkie miasta z kraju za każdym razem $store.state.country, gdy się zmieni, więc dodaję to do obserwatora. Wtedy napisałbym wezwanie Ajax: store.dispatch('fetchCities')piszę:axios.get('cities',{params:{country: state.country }}).then(response => store.commit('receiveCities',response) )
Andy
2

Można użyć kombinacji Vuex działań , pochłaniacze , obliczonych właściwości i obserwatorów słuchać zmian na wartości państwowego Vuex.

Kod HTML:

<div id="app" :style='style'>
  <input v-model='computedColor' type="text" placeholder='Background Color'>
</div>

Kod JavaScript:

'use strict'

Vue.use(Vuex)

const { mapGetters, mapActions, Store } = Vuex

new Vue({
    el: '#app',
  store: new Store({
    state: {
      color: 'red'
    },
    getters: {
      color({color}) {
        return color
      }
    },
    mutations: {
      setColor(state, payload) {
        state.color = payload
      }
    },
    actions: {
      setColor({commit}, payload) {
        commit('setColor', payload)
      }
    }
  }),
  methods: {
    ...mapGetters([
        'color'
    ]),
    ...mapActions([
        'setColor'
    ])
  },
  computed: {
    computedColor: {
        set(value) {
        this.setColor(value)
      },
      get() {
        return this.color()
      }
    },
    style() {
        return `background-color: ${this.computedColor};`
    }
  },
  watch: {
    computedColor() {
        console.log(`Watcher in use @${new Date().getTime()}`)
    }
  }
})

Zobacz demo JSFiddle .

Amin NAIRI
źródło
1

Możesz również zapisać się na mutacje sklepu:

store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
})

https://vuex.vuejs.org/api/#subscribe

stopowanie
źródło
Możesz to uruchomić w hooku beforeMount () twojego komponentu, a następnie filtrować nadchodzące mutacje za pomocą instrukcji if. np. if (mutation.type == "names / SET_NAMES") {... zrób coś}
Alejandro
1

Wewnątrz komponentu utwórz obliczoną funkcję

computed:{
  myState:function(){
    return this.$store.state.my_state; // return the state value in `my_state`
  }
}

Teraz można obserwować obliczoną nazwę funkcji, np

watch:{
  myState:function(newVal,oldVal){
    // this function will trigger when ever the value of `my_state` changes
  }
}

Zmiany dokonane w vuexstanie my_statezostaną odzwierciedlone w obliczonej funkcji myStatei wyzwolą funkcję zegarka.

Jeśli stan my_statema zagnieżdżone dane, handleropcja pomoże bardziej

watch:{
  myState:{
    handler:function(newVal,oldVal){
      // this function will trigger when ever the value of `my_state` changes
    },
    deep:true
  }
}

Spowoduje to obejrzenie wszystkich zagnieżdżonych wartości w sklepie my_state.

Rijosh
źródło
0

Możesz także użyć mapState w komponencie vue, aby bezpośrednio pobrać stan ze sklepu.

W twoim komponencie:

computed: mapState([
  'my_state'
])

Gdzie my_statejest zmienna ze sklepu.

Eugene Kulakov
źródło
0

====== store =====
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    showRegisterLoginPage: true,
    user: null,
    allitem: null,
    productShow: null,
    userCart: null
  },
  mutations: {
    SET_USERS(state, payload) {
      state.user = payload
    },
    HIDE_LOGIN(state) {
      state.showRegisterLoginPage = false
    },
    SHOW_LOGIN(state) {
      state.showRegisterLoginPage = true
    },
    SET_ALLITEM(state, payload) {
      state.allitem = payload
    },
    SET_PRODUCTSHOW(state, payload) {
      state.productShow = payload
    },
    SET_USERCART(state, payload) {
      state.userCart = payload
    }
  },
  actions: {
    getUserLogin({ commit }) {
      axios({
        method: 'get',
        url: 'http://localhost:3000/users',
        headers: {
          token: localStorage.getItem('token')
        }
      })
        .then(({ data }) => {
          // console.log(data)
          commit('SET_USERS', data)
        })
        .catch(err => {
          console.log(err)
        })
    },
    addItem({ dispatch }, payload) {
      let formData = new FormData()
      formData.append('name', payload.name)
      formData.append('file', payload.file)
      formData.append('category', payload.category)
      formData.append('price', payload.price)
      formData.append('stock', payload.stock)
      formData.append('description', payload.description)
      axios({
        method: 'post',
        url: 'http://localhost:3000/products',
        data: formData,
        headers: {
          token: localStorage.getItem('token')
        }
      })
        .then(({ data }) => {
          // console.log('data hasbeen created ', data)
          dispatch('getAllItem')
        })
        .catch(err => {
          console.log(err)
        })
    },
    getAllItem({ commit }) {
      axios({
        method: 'get',
        url: 'http://localhost:3000/products'
      })
        .then(({ data }) => {
          // console.log(data)
          commit('SET_ALLITEM', data)
        })
        .catch(err => {
          console.log(err)
        })
    },
    addUserCart({ dispatch }, { payload, productId }) {
      let newCart = {
        count: payload
      }
      // console.log('ini dari store nya', productId)

      axios({
        method: 'post',
        url: `http://localhost:3000/transactions/${productId}`,
        data: newCart,
        headers: {
          token: localStorage.getItem('token')
        }
      })
        .then(({ data }) => {
          dispatch('getUserCart')
          // console.log('cart hasbeen added ', data)
        })
        .catch(err => {
          console.log(err)
        })
    },
    getUserCart({ commit }) {
      axios({
        method: 'get',
        url: 'http://localhost:3000/transactions/user',
        headers: {
          token: localStorage.getItem('token')
        }
      })
        .then(({ data }) => {
          // console.log(data)
          commit('SET_USERCART', data)
        })
        .catch(err => {
          console.log(err)
        })
    },
    cartCheckout({ commit, dispatch }, transactionId) {
      let count = null
      axios({
        method: 'post',
        url: `http://localhost:3000/transactions/checkout/${transactionId}`,
        headers: {
          token: localStorage.getItem('token')
        },
        data: {
          sesuatu: 'sesuatu'
        }
      })
        .then(({ data }) => {
          count = data.count
          console.log(count, data)

          dispatch('getUserCart')
        })
        .catch(err => {
          console.log(err)
        })
    },
    deleteTransactions({ dispatch }, transactionId) {
      axios({
        method: 'delete',
        url: `http://localhost:3000/transactions/${transactionId}`,
        headers: {
          token: localStorage.getItem('token')
        }
      })
        .then(({ data }) => {
          console.log('success delete')

          dispatch('getUserCart')
        })
        .catch(err => {
          console.log(err)
        })
    }
  },
  modules: {}
})

semua bisa
źródło
1
Witamy na stronie. Umieszczenie samego fragmentu kodu nie wystarczy. Podaj wyjaśnienia dotyczące swojego kodu.
pgk
0

Użyłem w ten sposób i działa:

store.js:

const state = {
  createSuccess: false
};

mutations.js

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

działania.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
}

A w komponencie, w którym używasz stanu ze sklepu:

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

Gdy użytkownik przesyła formularz, zostanie wywołana akcja STORE , po pomyślnym utworzeniu mutacja CREATE_SUCCESS zostanie zatwierdzona . Włącz createSuccess ma wartość true, aw komponencie watcher zobaczy zmianę wartości i wyzwoli powiadomienie.

isSuccess powinno być zgodne z nazwą zadeklarowaną w getters.js

Vuong Tran
źródło