web-dev-qa-db-fra.com

Comment définir l'état initial dans Vuex 2?

J'utilise Vue.js 2.0 et Vuex 2.0 pour une petite application. J'initialise le magasin dans le hook de cycle de vie 'créé' à la racine Vue) en appelant une action qui extrait l'état initial d'une API ... comme dans mon - composant racine:

const app = new Vue({
 el: "#app",
 router,
 store,
 data: {
     vacation: {},
 },
 components: {
    'vacation-status': VacationStatus,
 },
 created(){
    //initialize store data structure by submitting action.
    this.$store.dispatch('getVacation');
 },
 computed: {
 },
 methods: {
 }
});

Cela fonctionne très bien. Voici l'action sur mon magasin que j'appelle ici:

getVacation({commit}){
  api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
}

La mutation que cela commet avec 'UPDATE_VACATION' est ici:

[UPDATE_VACATION] (state, payload) {
  state.vacation = payload.vacation;
},

Mon problème: Lorsque je charge l'application, tous mes composants qui "obtiennent" des valeurs du magasin génèrent des erreurs. J'essaie d'accéder aux valeurs "non définies" du magasin. En d'autres termes, l'état n'a pas encore été initialisé.

Par exemple, j'ai un composant qui a des accesseurs composants enfants comme ceci:

computed: {
        arrival () {
            return this.$store.getters.arrival
        },
        departure() {
            return this.$store.getters.departure
        },
        countdown: function() {
            return this.$store.getters.countdown
        }
} 

Tous ces accesseurs génèrent des erreurs car 'vacation' n'est pas défini sur l'objet state. Cela me semble être un problème asynchrone, mais pourrait être faux. Est-ce que j'initialise mon magasin au mauvais endroit?

  Vue.use(Vuex);

  export default new Vuex.Store({
    state: {},
    getters: {
    getVacation: state => {
        return state.vacation
    },
    guests: state => {
        return state.vacation.guests
    },
    verifiedGuests: state => {
        return state.vacation.guests.filter(guest => guest.verified)
    },
    emergencyContacts: state => {
        return state.emergency_contacts
    },
    arrival: state => {
        return state.vacation.check_in
    },
    departure: state => {
        return state.vacation.check_out
    },
    countdown : state => {
        let check_in = new Date(state.vacation.check_in);
        let now = new Date();

        if ((now - check_in) > 0) {
            return 'This vacation started on ' + check_in;
        }

        let difference = check_in - now;
        let day = 1000 * 60 * 60 * 24;

        return Math.ceil(difference / day) + " days until your vacation";
    }
},
mutations: {
    [UPDATE_VACATION] (state, payload) {
        state.vacation = payload.vacation;
    },
    [ADD_GUEST] (state, payload) {
        state.vacation.guests.Push(payload.guest);
    },
    [REMOVE_GUEST] (state, payload){
        state.vacation.guests.filter(guest => { debugger; return guest.id != payload.guest.id})
    },
    [UPDATE_GUEST] (state, payload){
        state.vacation.guests.map(guest => {
            // Refactor Object.assign to deep cloning of object
            return guest.id === payload.guest.id ? Object.assign({}, guest, payload.guest) : guest;
        })
    },
    [ADD_EMERGENCY] (state, payload){
        state.vacation.emergency_contacts.Push(payload.emergency_contact)
    },
    [REMOVE_EMERGENCY] (state, payload){
        state.vacation.emergency_contacts.filter(contact => contact.id !== payload.emergency_contact.id)
    },
    [UPDATE_EMERGENCY] (state, payload){
        state.vacation.emergency_contacts.map(contact => {
            // Refactor not needed because emergency_contact is a shallow object.
           return contact.id === payload.emergency_contact.id ? Object.assign({}, contact, payload.emergency_contact) : contact;
        });
    }
},
actions: {
    getVacation({commit}){
      api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
    },
    addGuest({commit}, guest){
        commit(ADD_GUEST, guest);
    },
    removeGuest({commit}, guest){
        commit(REMOVE_GUEST, guest);
    },
    updateGuest({commit}, guest){
        commit(UPDATE_GUEST, guest);
    },
    addEmergency({commit}, guest){
        commit(ADD_EMERGENCY, contact)
    },
    removeEmergency({commit}, contact){
        commit(REMOVE_EMERGENCY, contact)
    },
    updateEmergency({commit}, contact){
        commit(UPDATE_EMERGENCY, contact)
    },
    updateServer(store, payload){
      return api.saveVacation(payload)
    }
}

});

Pour que la solution soit claire pour les autres:

Je ne plaçais pas mon état initial assez correctement dans le magasin lui-même. Je récupérais les données et mettais le magasin à jour correctement, mais le magasin devait être initialisé comme suit:

export default new Vuex.Store({
 state: {
     vacation: {}//I added this, and then justed updated this object on create of the root Vue Instance
 },
});
41
calbear47

Je pense que vous faites tout bien. Peut-être que vous ne créez pas correctement les accesseurs (vous ne pouvez voir aucune définition dans votre code). Ou bien vous ne définissez pas correctement l'état initial (également non visible dans votre extrait).

J'utiliserais mapState pour que les propriétés d'état soient disponibles dans les composants.

Dans la démo, ajoutez simplement users au tableau dans le paramètre de méthode mapState et les données des utilisateurs seront disponibles sur le composant. (Je viens d'ajouter le getter users pour montrer comment cela fonctionne. Ce n'est pas nécessaire si vous utilisez mapState.)

Veuillez regarder la démo ci-dessous ou ceci violon .

const api =
  'https://jsonplaceholder.typicode.com/users'

const UPDATE_USERS = 'UPDATE_USERS'
const SET_LOADING = 'SET_LOADING'

const store = new Vuex.Store({
  state: {
    users: {},
    loading: false
  },
  mutations: {
    [UPDATE_USERS](state, users) {
      console.log('mutate users', users)
      state.users = users;
      console.log(state)
    }, [SET_LOADING](state, loading) {
      state.loading = loading;
    }
  },
  getters: {
    users(state) {
      return state.users
    }
  },
  actions: {
    getUsers({commit}) {
      commit(SET_LOADING, true);
      return fetchJsonp(api)
        .then((users) => users.json())
        .then((usersParsed) => {
          commit(UPDATE_USERS, usersParsed)
          commit(SET_LOADING, false)
        })
    }
  }
})

const mapState = Vuex.mapState;

const Users = {
  template: '<div><ul><li v-for="user in users">{{user.name}}</li></ul></div>',
  computed: mapState(['users'])
}

new Vue({
  el: '#app',
  store: store,
  computed: {
    ...mapState(['loading']),
      //...mapState(['users']),
      /*users () { // same as mapState
        return this.$store.state.users;
      }*/
      users() { // also possible with mapGetters(['users'])
        return this.$store.getters.users
      }
  },
  created() {
    this.$store.dispatch('getUsers')
  },
  components: {
    Users
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch-jsonp/1.0.5/fetch-jsonp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.1.1/vuex.min.js"></script>
<div id="app">
  <div v-if="loading">loading...</div>
  <users></users>
  <pre v-if="!loading">{{users}}</pre>
</div>
23
AWolf