Powracające obietnice z działań Vuex

130

Niedawno zacząłem migrować rzeczy z jQ do bardziej ustrukturyzowanego frameworka jakim jest VueJS i uwielbiam to!

Koncepcyjnie Vuex nieco zmienił dla mnie paradygmat, ale jestem pewien, że wiem teraz, o co w tym wszystkim chodzi i całkowicie to rozumiem! Ale istnieje kilka małych szarych obszarów, głównie z punktu widzenia implementacji.

Ten, który uważam za dobry z założenia, ale nie wiem, czy jest sprzeczny z cyklem Vuex w jednokierunkowym przepływie danych.

Zasadniczo, czy zwracanie obiektu obietnicy (podobnego do) z działania jest uważane za dobrą praktykę? Traktuję je jako opakowania asynchroniczne, ze stanami niepowodzenia i tym podobnymi, więc wydaje się, że dobrze pasuje do zwrotu obietnicy. Przeciwnie, mutatory po prostu zmieniają rzeczy i są czystymi strukturami w sklepie / module.

Daniel Park
źródło

Odpowiedzi:

255

actionsw Vuex są asynchroniczne. Jedynym sposobem, aby powiadomić funkcję wywołującą (inicjatora akcji), że akcja została zakończona - jest zwrócenie obietnicy i rozpatrzenie jej później.

Oto przykład: myActionzwraca a Promise, wykonuje wywołanie http i rozwiązuje lub odrzuca Promisepóźniejsze - wszystko asynchronicznie

actions: {
    myAction(context, data) {
        return new Promise((resolve, reject) => {
            // Do something here... lets say, a http call using vue-resource
            this.$http("/api/something").then(response => {
                // http success, call the mutator and change something in state
                resolve(response);  // Let the calling function know that http is done. You may send some data back
            }, error => {
                // http failed, let the calling function know that action did not work out
                reject(error);
            })
        })
    }
}

Teraz, gdy Twój komponent Vue zostanie zainicjowany myAction, otrzyma ten obiekt Promise i będzie mógł wiedzieć, czy się powiodło, czy nie. Oto przykładowy kod komponentu Vue:

export default {
    mounted: function() {
        // This component just got created. Lets fetch some data here using an action
        this.$store.dispatch("myAction").then(response => {
            console.log("Got some data, now lets show something in this component")
        }, error => {
            console.error("Got nothing from server. Prompt user to check internet connection and try again")
        })
    }
}

Jak widać powyżej, actionszwrot pliku Promise. W przeciwnym razie inicjator akcji nie będzie wiedział, co się dzieje i kiedy sytuacja jest na tyle stabilna, aby wyświetlić coś w interfejsie użytkownika.

I ostatnia uwaga dotycząca mutators- jak słusznie zauważyłeś, są one synchroniczne. Zmieniają rzeczy w statei zwykle są wywoływani z actions. Nie ma potrzeby mieszania Promisesz tą częścią mutatorsjak actionsuchwyt.

Edycja: Moje poglądy na cykl Vuex jednokierunkowego przepływu danych:

Jeśli uzyskujesz dostęp do danych, tak jak this.$store.state["your data key"]w swoich komponentach, przepływ danych jest jednokierunkowy.

Obietnicą działania jest jedynie poinformowanie komponentu, że działanie jest zakończone.

Komponent może albo pobierać dane z funkcji obietnicy rozwiązania w powyższym przykładzie (nie jednokierunkowe, dlatego nie jest zalecane) lub bezpośrednio, z $store.state["your data key"]której jest jednokierunkowy i podąża za cyklem życia danych vuex.

Powyższy akapit zakłada, że ​​Twój mutator używa Vue.set(state, "your data key", http_data), gdy wywołanie http zostanie zakończone w Twojej akcji.

Mani
źródło
4
„Jak widać powyżej, bardzo korzystne jest, aby działania zwracały Obietnicę. W przeciwnym razie inicjator akcji nie będzie wiedział, co się dzieje i kiedy wszystko jest wystarczająco stabilne, aby pokazać coś w interfejsie użytkownika”. IMO, to nie ma sensu Vuex. Inicjator akcji nie powinien wiedzieć, co się dzieje. Akcja powinna mutować stan, gdy dane wracają ze zdarzenia asynchronicznego, a komponent powinien reagować na tę zmianę etapu w oparciu o stan sklepu Vuex, a nie obietnicę.
ceejayoz
1
@ceejayoz Zgoda, państwo powinno być jedynym źródłem prawdy dla wszystkich obiektów danych. Ale obietnica to jedyny sposób, aby skontaktować się z inicjatorem akcji. Na przykład, jeśli chcesz wyświetlić przycisk „Spróbuj ponownie” po niepowodzeniu protokołu HTTP, ta informacja nie może przejść do stanu, ale może zostać przesłana z powrotem tylko za pośrednictwem pliku Promise.reject().
Mani
1
Można to łatwo załatwić w sklepie Vuex. Sama akcja może odpalić failedmutator, który ustawia state.foo.failed = true, z którym komponent może sobie poradzić. Nie potrzeba obietnicy być przekazywane do komponentu o tym, i jako bonus, coś innego , że chce, aby reagować na tej samej awarii można zrobić ze sklepu też.
ceejayoz
4
@ceejayoz Sprawdź akcje tworzenia (ostatnia sekcja) w dokumentacji - vuex.vuejs.org/en/actions.html - akcje są asynchroniczne i dlatego zwracanie obietnicy jest dobrym pomysłem, jak stwierdzono w tych dokumentach. Może nie w powyższym przypadku $ http, ale w innym przypadku możemy potrzebować wiedzieć, kiedy akcja jest zakończona.
Mani
6
@DanielPark Tak, „to zależy” od scenariusza i indywidualnych preferencji dewelopera. W moim przypadku chciałem uniknąć wartości pośrednich, takich jak {isLoading:true}w moim stanie, dlatego skorzystałem z obietnic. Twoje preferencje mogą się różnić. Ostatecznie naszym celem jest napisanie kodu bez bałaganu i łatwego w utrzymaniu. To, czy obietnica osiągnie ten cel, czy stan vuex - zależy od indywidualnych programistów i zespołów.
Mani
41

Tylko dla informacji na zamknięty temat: nie musisz tworzyć obietnicy, axios sam ją zwraca:

Odniesienie: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4

Przykład:

    export const loginForm = ({ commit }, data) => {
      return axios
        .post('http://localhost:8000/api/login', data)
        .then((response) => {
          commit('logUserIn', response.data);
        })
        .catch((error) => {
          commit('unAuthorisedUser', { error:error.response.data });
        })
    }

Inny przykład:

    addEmployee({ commit, state }) {       
      return insertEmployee(state.employee)
        .then(result => {
          commit('setEmployee', result.data);
          return result.data; // resolve 
        })
        .catch(err => {           
          throw err.response.data; // reject
        })
    }

Kolejny przykład z async-await

    async getUser({ commit }) {
        try {
            const currentUser = await axios.get('/user/current')
            commit('setUser', currentUser)
            return currentUser
        } catch (err) {
            commit('setUser', null)
            throw 'Unable to fetch current user'
        }
    },
Anoop.PA
źródło
Czy ostatni przykład nie powinien być nadmiarowy, ponieważ akcje axios są domyślnie już asynchroniczne?
nonNumericalFloat
9

działania

ADD_PRODUCT : (context,product) => {
  return Axios.post(uri, product).then((response) => {
    if (response.status === 'success') {  
      context.commit('SET_PRODUCT',response.data.data)
    }
    return response.data
  });
});

Składnik

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})
Bhaskararao Gummidi
źródło
2
ta nie działa odpowiedź undefined w komponencie
Nand Lal
1
Myślę, że zapomniałeś dodać zwrotu w funkcji ADD_PRODUCT
Bhaskararao Gummidi
Powinien być pisany małą literą „a” w „axios”.
bigp
Wziąłem Axois jako const, które importuje z 'axios'
Bhaskararao Gummidi
0

TL: DR; zwracaj obietnice z twoich działań tylko wtedy, gdy jest to konieczne, ale SUCHO łącząc te same działania.

Przez długi czas myślałem też, że powracające akcje są sprzeczne z cyklem Vuex jednokierunkowego przepływu danych.

Ale istnieją SPRAWY EDGE gdzie powrocie obietnicę od twoich działań może być „konieczne”.

Wyobraź sobie sytuację, w której akcję można wyzwolić z 2 różnych komponentów, a każdy z nich inaczej obsługuje przypadek awarii. W takim przypadku należałoby przekazać komponent wywołujący jako parametr, aby ustawić różne flagi w sklepie.

Głupi przykład

Strona, na której użytkownik może edytować nazwę użytkownika w pasku nawigacyjnym i na stronie / profile (która zawiera pasek nawigacyjny). Oba wyzwalają akcję „zmień nazwę użytkownika”, która jest asynchroniczna. Jeśli obietnica nie powiedzie się, strona powinna wyświetlać błąd tylko w komponencie, z którego użytkownik próbował zmienić nazwę użytkownika.

Oczywiście to głupi przykład, ale nie widzę sposobu rozwiązania tego problemu bez powielania kodu i wykonywania tego samego wywołania w 2 różnych akcjach.

srmico
źródło
-1

działania.js

const axios = require('axios');
const types = require('./types');

export const actions = {
  GET_CONTENT({commit}){
    axios.get(`${URL}`)
      .then(doc =>{
        const content = doc.data;
        commit(types.SET_CONTENT , content);
        setTimeout(() =>{
          commit(types.IS_LOADING , false);
        } , 1000);
      }).catch(err =>{
        console.log(err);
    });
  },
}

home.vue

<script>
  import {value , onCreated} from "vue-function-api";
  import {useState, useStore} from "@u3u/vue-hooks";

  export default {
    name: 'home',

    setup(){
      const store = useStore();
      const state = {
        ...useState(["content" , "isLoading"])
      };
      onCreated(() =>{
        store.value.dispatch("GET_CONTENT" );
      });

      return{
        ...state,
      }
    }
  };
</script>
Chris Michael
źródło