vue.js 2 como assistir a valores de loja da vuex

168

Estou usando vuexe vuejs 2juntos.

Eu sou novo vuex, quero assistir a uma storemudança variável.

Eu quero adicionar a watchfunção no meuvue component

Isto é o que eu tenho até agora:

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

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

Quero saber se há alguma alteração no my_state

Como eu assisto store.my_stateno meu componente vuejs?

raffffffff
fonte
usar um plugin Vuejs que vem com cromo ele vai vir a calhar
AKASH PANDEY

Respostas:

206

Digamos, por exemplo, que você tem uma cesta de frutas e, sempre que adicionar ou remover uma fruta da cesta, você deseja (1) exibir informações sobre a contagem de frutas, mas também (2) deseja ser notificado sobre a contagem das frutas de alguma maneira chique ...

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>

Observe que o nome da função no watchobjeto deve corresponder ao nome da função no computedobjeto. No exemplo acima, o nome é count.

Os valores novos e antigos de uma propriedade monitorada serão passados ​​para o retorno de chamada da monitoração (a função de contagem) como parâmetros.

A loja de cestas pode ficar assim:

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

Você pode ler mais nos seguintes recursos:

Anastazy
fonte
Estou apenas imaginando o que devo fazer quando a watchação deve ser dividida em duas etapas: 1) Primeiro, verificando se os dados desejados estão em cache e se apenas retornam os dados em cache; 2) Se o cache falhou, preciso de uma ação assíncrona do ajax para buscar os dados, mas isso parece ser o actiontrabalho. Esperando que minha pergunta faça sentido, obrigado!
1Cr18Ni9
Qual é o benefício disso, em relação à resposta do micah5, que apenas define um observador no componente, no valor da loja? Tem menos código para manter.
Exocentric
@Exocentric Quando escrevi a resposta, a pergunta não estava clara para mim. Não há contexto por que é necessário observar propriedades. Pense nisso como: "Eu quero assistir a variável X, para que eu possa fazer Y." Provavelmente é por isso que a maioria das respostas propõe abordagens tão diferentes. Ninguém sabe qual é a intenção. Por isso, incluí "objetivos" na minha resposta. Se você tiver objetivos diferentes, uma resposta diferente pode ser adequada a eles. Meu exemplo é apenas um ponto de partida para experimentação. Não pretende ser uma solução plug & play. Não há "benefício", porque os benefícios dependem da sua situação.
Anastazy
@ 1Cr18Ni9 Acho que o cache não pertence ao código do componente. Você acabará criando algo em excesso que deve ser realmente simples (buscando dados e vinculando-os à visualização). O cache já está implementado no navegador. Você pode tirar vantagem disso enviando cabeçalhos corretos do servidor. Explicação simples aqui: csswizardry.com/2019/03/cache-control-for-civilians . Você também pode dar uma olhada nos ServiceWorkers, que permitem que o site funcione mesmo sem conexão com a Internet.
Anastazy
61

Você não deve usar observadores do componente para ouvir a mudança de estado. Eu recomendo que você use funções getters e mapeie-as dentro do seu componente.

import { mapGetters } from 'vuex'

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

Na sua loja:

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

Você deve poder ouvir as alterações feitas em sua loja usando this.myStateem seu componente.

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

Gabriel Robert
fonte
1
Não sei como implementar mapGetters. Você pode me indicar um exemplo? Seria uma grande ajuda. Acabei de implementar a resposta GONG no momento. TY
Rbex
1
@Rbex "mapGetters" faz parte da biblioteca 'vuex'. Você não precisa implementá-lo.
Gabriel Robert
69
Esta resposta está errada. Ele realmente precisa observar as propriedades calculadas.
Juan
15
O getter chamado uma vez recuperará o estado apenas naquele momento. Se você deseja que essa propriedade reflita a mudança de estado de outro componente, você deve observá-la.
C #: Tierney
3
Por que "você não deve usar observadores de componentes para ouvir a mudança de estado"? Aqui está um exemplo em que você pode não pensar, se eu quiser assistir ao token do estado e quando ele mudar para redirecionar para outra página. Portanto, há alguns casos em que você precisa fazer isso. talvez você precise de mais experiência para saber disso.
Shlomi Levi 29/09
46

É tão simples quanto:

watch: {
  '$store.state.drawer': function() {
    console.log(this.$store.state.drawer)
  }
}
micah5
fonte
6
Esta é uma visão muito mais direta do que qualquer uma das respostas aqui ... existe algum argumento contra fazer isso ..?
Inigo
19
É muito simples, portanto, não se parece com js, js deve ser mais complicado.
digout 28/11/19
1
Seria ainda mais simples se fossefunction(n) { console.log(n); }
WofWca 17/02
2
Muito legal. Interessado também em qualquer inconveniente dessa abordagem. Até agora, parece funcionar bem.
namero999 23/02
1
Seriamente. Isso parece muito melhor do que a resposta aceita, que requer nomes de funções duplicados no relógio e computados. Um especialista pode comentar por que ou por que não fazê-lo dessa maneira?
Exocêntrico
42

Como mencionado acima, não é uma boa ideia assistir as alterações diretamente na loja

Mas, em alguns casos muito raros, pode ser útil para alguém, então deixarei esta resposta. Para outros casos, consulte @ gabriel-robert answer

Você pode fazer isso completamente state.$watch. Adicione isso no seu createdmétodo (ou onde você precisar que ele seja executado) no componente

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

Mais detalhes: https://vuex.vuejs.org/api/#watch

GONGO
fonte
3
Não acho que seja uma boa ideia assistir diretamente ao estado. Deveríamos usar getters. vuex.vuejs.org/en/getters.html#the-mapgetters-helper
Gabriel Robert
14
@ GabrielRobert Acho que há um lugar para ambos. Se você precisar alterar reativamente as condições do modelo com base, usar um valor calculado com o mapState, etc faz sentido. Mas, caso contrário, como para controle de fluxo uniforme em um componente, você precisa de um relógio completo. Você está certo, você não deve usar observadores de componentes simples, mas o estado $ relógio é projetado para esses casos de uso.
roberto tomás
14
Todo mundo menciona, mas ninguém diz o porquê! Estou tentando criar uma loja vuex que seja sincronizada automaticamente com um banco de dados após as alterações. Eu sinto que os observadores na loja são a maneira mais sem atritos! O que você acha? Ainda não é uma boa ideia?
mesqueeb
16

Acho que o autor da pergunta quer usar o relógio com a Vuex.

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

      },
      {
        deep:true
      }
      );
yeahdixon
fonte
13

Isso é para todas as pessoas que não conseguem resolver seu problema com getters e realmente precisam de um observador, por exemplo, para conversar com coisas de terceiros que não são da área de vendas (consulte Vue Watchers sobre quando usar os observadores).

Os observadores e os valores calculados do componente Vue também funcionam em valores calculados. Portanto, não é diferente com o 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);
        }
    }
}

se se trata apenas de combinar estado local e global, o documento do mapState também fornece um exemplo:

computed: {
    ...mapState({
        // to access local state with `this`, a normal function must be used
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
    }
})
dube
fonte
bom truque, mas tedioso demais, você não acha?
Martian2049
2
Não é um truque se estiver nos documentos, não é? Mas então, não é um pró-argumento para vue / vuex quer
dube
8

se você usar o texto datilografado, poderá:

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

..

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

Zhang Sol
fonte
Por que exatamente isso foi prejudicado? Só porque a solução é para o componente de classe vue e o TO estava solicitando estilos antigos de classe de vue? Acho o primeiro preferível. Talvez o @Zhang Sol possa mencionar na introdução que isso é explicitamente para o componente de classe vue?
JackLeEmmerdeur 16/09/19
Observe com certeza por que um decorador datilografado seria preferível a uma solução nativa do vue tão simples como esta: stackoverflow.com/a/56461539/3652783
yann_yinn
6

Crie um estado local da sua variável de loja assistindo e definindo as alterações de valor. De modo que a variável local seja alterada para o modelo v de entrada de formulário não modifique diretamente a variável de armazenamento .

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

A melhor maneira de observar as mudanças nas lojas é usar mapGetterscomo Gabriel disse. Mas há um caso em que você não pode fazê-lo, por mapGettersexemplo, deseja obter algo da loja usando o parâmetro:

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

nesse caso, você não pode usar mapGetters. Você pode tentar fazer algo assim:

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

Mas infelizmente todoById só será atualizado se this.idfor alterado

Se você deseja atualizar o componente, nesse caso, use a this.$store.watch solução fornecida pelo Gong . Ou lide com o componente conscientemente e atualize this.idquando precisar atualizar todoById.

Arseniy-II
fonte
obrigado. Isso é exatamente o meu caso de uso, e de fato o getter não pode ser visto, então ...
pscheit
5

Se você simplesmente deseja assistir a uma propriedade de estado e, em seguida, agir dentro do componente de acordo com as alterações dessa propriedade, consulte o exemplo abaixo.

Em store.js:

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

Nesse cenário, estou criando uma booleanpropriedade de estado que alterarei em diferentes locais do aplicativo, como segue:

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

Agora, se eu precisar observar essa propriedade do estado em algum outro componente e alterar a propriedade local, escreveria o seguinte no mountedgancho:

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

Em primeiro lugar, estou definindo um inspetor na propriedade state e, em seguida, na função de retorno de chamada, uso a valuepropriedade dessa para alterar o localProperty.

Espero que ajude!

Jakub A Suplicki
fonte
3

Quando você deseja assistir no nível do estado, isso pode ser feito da seguinte maneira:

let App = new Vue({
    //...
    store,
    watch: {
        '$store.state.myState': function (newVal) {
            console.log(newVal);
            store.dispatch('handleMyStateChange');
        }
    },
    //...
});
Andy
fonte
Não é uma boa ideia lidar com store.statemudanças por dispatchação de estado do componente, pois esse comportamento só funcionará se você usar esse componente. Além disso, você pode terminar com loop infinito. Relógio para store.statealterar raramente uso, por exemplo, se você tem um componente ou uma página que deve fazer alguma ação com base em store.statemudou isso não poderia tratados por meio calculado mapState apenas quando você não pode comparar newValuevsoldValue
Januartha
@ Januartha, qual é a sua sugestão para esse problema então?
Billal Begueradj 4/18
@ Andy sim, claro, o seu trabalho. Eu só quero notar por que você ligou store.dispatch? se você quiser lidar com store.statealterações para store' why not handle it inside store.mutations`?
Januartha
@BillalBEGUERADJ Eu prevejo que a solução do dube é mais limpa
Januartha
@ Januartha, porque pode haver uma chamada ajax para acontecer antes de fazer uma mutação, é por isso que eu uso store.dispatchprimeiro. Por exemplo, quero obter todas as cidades de um país sempre $store.state.countryque houver alterações, então adiciono isso ao observador. Então eu escreveria uma chamada ajax: em store.dispatch('fetchCities')eu escrevo: #axios.get('cities',{params:{country: state.country }}).then(response => store.commit('receiveCities',response) )
Andy Andy
2

Você pode usar uma combinação de ações , getters , propriedades computadas e observadores do Vuex para ouvir alterações no valor do estado do Vuex.

Código HTML:

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

Código 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()}`)
    }
  }
})

Veja a demonstração do JSFiddle .

Amin NAIRI
fonte
1

Você também pode assinar as mutações da loja:

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

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

liga
fonte
Você pode disparar isso no gancho beforeMount () do seu componente e filtrar as mutações recebidas com uma instrução if. por exemplo, se (mutation.type == "nomes / SET_NAMES") {...} fazer algo
Alejandro
1

Dentro do componente, crie uma função computada

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

Agora, o nome da função calculada pode ser observado, como

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

As alterações feitas no vuexestado my_staterefletirão na função computada myStatee acionarão a função de observação.

Se o estado my_stateestiver tendo dados aninhados, a handleropção ajudará mais

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

Isso observará todos os valores aninhados na loja my_state.

Rijosh
fonte
0

Você também pode usar o mapState no componente vue para direcionar o estado de obtenção da loja.

No seu componente:

computed: mapState([
  'my_state'
])

Onde my_stateé uma variável da loja.

Eugene Kulakov
fonte
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
fonte
1
Bem vindo ao site. Colocar apenas um trecho de código não é suficiente. Forneça algumas explicações sobre o seu código.
pgk 27/01
0

Eu usei desta maneira e funciona:

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
}

E no seu componente onde você usa o estado da loja:

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

Quando o usuário envia o formulário, a ação STORE será chamada, após o sucesso criado, a mutação CREATE_SUCCESS será confirmada depois disso. A opção createSuccess é true e, no componente, o observador verá que o valor foi alterado e acionará a notificação.

isSuccess deve corresponder ao nome que você declara em getters.js

Vuong Tran
fonte