web-dev-qa-db-fra.com

Rendu côté serveur VueJS: la propriété calculée ne voit pas les changements en magasin

J'essaie de faire fonctionner le rendu côté serveur dans VueJS.

J'ai suivi les documents officiels et j'essaie de faire fonctionner cet exemple en utilisant axios. Le point de terminaison est correct et les données s'affichent dans le mutation.

https://ssr.vuejs.org/guide/data.html

J'ai également trouvé cette page et essayé la plupart de ces exemples, qui incluent l'utilisation de getters, vuex mapState, mapGetter, etc.

vue.js 2 comment regarder les valeurs de stockage depuis vuex

Voici store/index.js:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

import { fetchPage } from './api'

export function createStore () {
    return new Vuex.Store({
        strict: true,

        state: () => ({
            res: {}
        }),

        actions: {
            actXY ({ commit }, page) {
                fetchPage('homepage').then((item) => {
                    commit('mutXY', { page, item });
                }).catch(error => {
                    throw new Error(`Store ${error}`);

                })
            }
        },

        mutations: {
            mutXY (state, { page, item }) {
                Vue.set(state.res, page, item);
                console.log(state.res.homepage.data)

            }
        },

        getters: {
            getXY (state) {
                return state.res
            }
        }
    })
}

le journal de la console affiche les informations correctes, ce qui signifie que le point de terminaison axios fonctionne et met à jour l'objet d'état. À l'origine, j'essayais d'appeler le mutation, mais j'ai depuis essayé d'appeler le getter. Malheureusement, les nouvelles données ne sont pas mises à jour dans le getter, donc bien sûr, rien de nouveau n'apparaît dans le composant.

C'est HomePage.vue:

<template>
  <div v-if="htmlData">{{ JSON.stringify(htmlData) }}</div>
  <div v-else>loading?</div>
</template>

<script>
export default ({
    name: 'homepage',

    computed: {
        htmlData () {
            console.log("computed = " + JSON.stringify(this.$store.state.res));
            console.log(this.$store.getters.getXY);
            return this.$store.getters
            // return this.getQuery()
        }
    },

    serverPrefetch () {
        return this.getData()
    },

    mounted () {
        console.log("in mounted")
        if (!this.item) {
            this.getData()
        }
    },

    methods: {
        getData () {
            return this.$store.dispatch('actXY', 'homepage')
        }
    }
})

</script>

Comme écrit, htmlData affichera:

{"getXY":{}}

Oui, j'ai essayé de nombreuses autres variantes sur la façon de retourner l'article du magasin dans le getter, mais rien ne fonctionne.

Outre les éléments des liens ci-dessus, j'ai également recherché des variations sur les fichiers de configuration, j'ai essayé d'ajouter async pour stocker/actions, getData (), etc.

J'ai également tenté de faire l'appel axios directement dans le composant, ce qui n'a pas eu de succès.

Puisqu'il s'agit d'un projet qui était déjà plus ou moins terminé dans VueJS que je convertis en SSR, j'ai tout supprimé de package.json et réinstallé chaque paquet, dans l'espoir que peut-être l'un des anciens vue packages provoquait un conflit.

J'ai également essayé de diviser le code de magasin à partir des documents officiels et essayé des variantes sur la façon dont les itinéraires sont écrits. Rien du tout ne fonctionne.

Je pense que je devrais ajouter ce que montrent les instructions d'impression lorsque j'exécute mon code actuel:

computed = undefined
computed mm  = undefined
getter = {}
computed get  = {"getXY":{}}
{
  title: 'Home'...}

La propriété calculée dans le composant s'exécute avant la mutation est définie. Cela provoque l'appel du getter avant la mise à jour de la mutation. De même, si j'essaie d'appeler des modifications de res directement à partir du composant, il n'y a rien dans les mutations lorsque le magasin est appelé et rendu.

Ceci est un dépôt du code que j'essaye d'exécuter: https://github.com/dt1/vue-ssr-attempt

(J'ai trouvé la réponse. J'ai mis à jour le dépôt avec ce qui fonctionne pour moi, et l'explication de ma solution est ci-dessous)

7
dizzystar

D'accord, j'ai finalement compris ça.

Les principaux problèmes que j'ai rencontrés concernaient les appels hors service. Lorsque j'ai corrigé la commande, je tombais sur Converting circular structure to JSON les erreurs. Ensuite, j'ai essayé de comprendre comment les rappels fonctionnent entre VueJS, les magasins Vuex et axios.

La solution finale que j'ai trouvée est:

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import * as util from 'util'

Vue.use(Vuex)

import { fetchPage } from './api'

export function createStore () {
    return new Vuex.Store({
        strict: true,

        state: () => ({
            res: {}
        }),

        actions: {
            actXY ({ commit }, page) {
                return fetchPage(page).then(item => {
                    commit('mutXY', { page , item } );
                })
            }
        },

        mutations: {
            mutXY (state, {page, item }) {
                Vue.set(state.res, page, item);
            }
        }
    })
}

Ensuite, lors de l'appel axios, je devais être sûr de ne renvoyer que le response.data au lieu de la réponse entière. Avant cela, je reçois un unhandled promise Erreur.

api.js

import Vue from 'vue';
import axios from 'axios';
import VueAxios from 'vue-axios'

Vue.use(VueAxios, axios);

Vue.axios.defaults.baseURL = '<url goes here>';

export function fetchPage(page){
    return Vue.axios.get(page).then((resp => resp.data))
}

Dernier point mais non le moindre, c'est Homepage.vue.

Pour déclencher une mise à jour, j'ai dû rendre getData () asynchrone:

<template>
<div v-if="htmlData">
  <div>{{ htmlData.title }}</div>
  <div>{{ htmlData.description }}</div>
  <div>{{ htmlData.html }}</div>
</div>
</template>

<script>
export default ({
    name: 'homepage',
    computed: {
        htmlData () {
            return this.$store.state.res.homepage
        }
    },

    serverPrefetch () {
        return this.getData()
    },
    mounted () {
        if (!this.item) {
            this.getData()
        }
    },
    methods: {
        async getData () {
            await this.$store.dispatch('actXY', 'homepage')
        }
    }
})
</script>

Espérons que cela aide tous ceux qui rencontrent tous ces problèmes.

1
dizzystar

Il n'y a pas de SSR dans Vue.js hors de la boîte . Dans votre exemple, vous pouvez obtenir des données sur le client.

Essayez cet exemple pour Store:

export default new Vuex.Store({
  strict: true,
  state: {
    res: null
  },
  getters: {
    getXY(state) {
      return state.res;
    }
  },
  mutations: {
    mutXY(state, payload) {
      state.res = payload;
    }
  },
  actions: {
    actXY: function(store) {
      fetchPage("homepage")
        .then(res => {
          store.commit("mutXY", res);
        })
        .catch(error => {
          throw new Error(`Store ${error}`);
        });
    }
  }
});

Et vous pouvez obtenir des données dans le composant en tant que telles:

  computed: {
    htmlData() {
      return this.$store.getters.getXY;
    }
  },
  created() {
    this.getData();
  },
  methods: {
    getData() {
      return this.$store.dispatch("actXY", "homepage");
    }
  }
1
David Go

Ma question est pourquoi la this.getData() est-elle appelée dans monté?

Si je comprends bien le processus, lorsque vous appelez le rendu côté serveur, vous essayez de rendre le HTML avant que le composant vue ne soit monté. Cela fonctionnerait-il si vous appeliez this.getData() dans créé au lieu de monté?

D'autres choses que j'essaierais:

  1. hardcode htmlData valeur dans le composant pour vérifier s'il rendrait
  2. si cela fonctionne, codez en dur la valeur htmlData en magasin et vérifiez si le composant peut récupérer ces données de stockage et rendre correctement aussi
  3. après cela fonctionne aussi, récupérez htmlData du serveur via la distribution du composant, voyez si cela fonctionne.
  4. si (3) ne fonctionne pas, essayez de pousser le htmlData vers un composant enfant qui est créé uniquement lorsque vous avez déjà htmlData prêt, envoyez htmlData au composant enfant via les accessoires
1
Stefani