Повернення обіцянок від дій Vuex


130

Нещодавно я почав мігрувати речі з jQ на більш структуровану структуру VueJS, і мені це подобається!

Концептуально Vuex трохи змінив парадигму для мене, але я впевнений, що я знаю, про що це все зараз, і цілком зрозумів це! Але є кілька сірих ділянок, переважно з точки зору впровадження.

Цей, на мій погляд, гарний дизайн, але не знаю, чи він суперечить циклу Vuex однонаправленого потоку даних.

В основному, чи вважається доброю практикою повернути об’єкт з обіцянками (-подобним)? Я ставлюся до них як до асинхронних обгортків, які мають стан відмови та подібне, тому здається гарним справою повернути обіцянку. Мутатори, навпаки, просто змінюють речі і є чистими структурами в магазині / модулі.

Відповіді:


255

actionsу Vuex є асинхронними. Єдиний спосіб дозволити функції виклику (ініціатора дії) знати, що дія завершена - це повернути Обіцяння та вирішити його пізніше.

Ось приклад: myActionповертає a Promise, здійснює http-дзвінок і Promiseпізніше вирішує або відхиляє - все асинхронно

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

Тепер, коли ваш компонент Vue ініціює myAction, він отримає цей об’єкт Promise і може знати, вдався він чи ні. Ось приклад коду для компонента 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")
        })
    }
}

Як ви бачите вище, actionsповернути алімент дуже корисноPromise . В іншому випадку ініціатор дії не може знати, що відбувається і коли все є досить стабільним, щоб щось відображати в інтерфейсі користувача.

І остання примітка щодо mutators- як ви правильно зазначали, вони є синхронними. Вони змінюють речі в state, і, як правило, викликаються зactions . Там немає необхідності змішувати Promisesз mutators, як actionsвпоратися з цією частиною.

Редагувати: Мої погляди на цикл Vuex однонаправленого потоку даних:

Якщо ви отримуєте доступ до даних, як this.$store.state["your data key"]у своїх компонентах, то потік даних односторонній.

Обіцянка від дії полягає лише в тому, щоб повідомити компоненту, що дія завершена.

Компонент може або приймати дані з функції вирішення обіцянок у наведеному вище прикладі (не однонаправлений, тому не рекомендується), або безпосередньо з $store.state["your data key"] якого є односпрямованим і слідкує за життєвим циклом даних vuex.

У наведеному вище абзаці передбачається, що ваш мутатор використовує Vue.set(state, "your data key", http_data), як тільки http-дзвінок завершиться у вашій дії.


4
"Як ви бачите вище, вкрай вигідно для дій повернути Обіцянку. Інакше ініціатор дії не може знати, що відбувається, і коли речі є досить стабільними, щоб щось відобразити в інтерфейсі користувача." ІМО, це не вистачає точки Vuex. Ініціатору дій не потрібно знати, що відбувається. Дія повинна мутувати стан, коли дані повертаються з асинхронної події, і компонент повинен відповідати на зміну етапу на основі стану магазину Vuex, а не Обіцянки.
ceejayoz

1
@ceejayoz Погоджено, що держава повинна бути єдиним джерелом істини для всіх об'єктів даних. Але Обіцянка - це єдиний спосіб повернутися до ініціатора дій. Наприклад, якщо ви хочете показати кнопку "Спробуйте знову" після відмови http, ця інформація не може перейти у стан, але вона може бути передана назад лише через Promise.reject().
Мані

1
З цим легко впоратися в магазині Vuex. Сама дія може запустити failedмутатор, який встановлює state.foo.failed = true, з яким може впоратися компонент. Немає необхідності в обіцянці не повинні бути передані в компонент для цього, і в якості бонусу, все інше , що хоче , щоб реагувати на той же провал може зробити це з магазину теж.
ceejayoz

4
@ceejayoz Ознайомтеся зі складанням дій (останній розділ) у документах - vuex.vuejs.org/en/action.html - дії є асинхронними, тому повернення Обіцянки є хорошою ідеєю, як зазначено в цих документах. Можливо, не у випадку http http, наведеному вище, але в іншому випадку нам може знатися, коли дія буде завершена.
Мані

6
@DanielPark Так, це залежить від сценарію та індивідуальних переваг розробника. У своєму випадку я хотів уникнути проміжних цінностей, як {isLoading:true}у моєму стані, і тому вдався до Обіцянь. Ваші уподобання можуть відрізнятися. Зрештою, наша мета - написати безгромодний і доступний код. Чи обіцянка досягне цієї мети, або стан vuex - залишається вирішувати окремим розробникам та командам.
Мані

41

Тільки для інформації щодо закритої теми: вам не потрібно створювати обіцянку, axios повертає один:

Посилання: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4

Приклад:

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

Ще один приклад:

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

Ще один приклад з функцією 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'
        }
    },

Чи не повинен останній приклад бути зайвим, оскільки дії axios за замовчуванням вже є асинхронними?
nonNumericalFloat

9

Дії

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

Компонент

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})


1
Я думаю, ви забули додати віддачу у функції ADD_PRODUCT
Bhaskararao Gummidi

У "аксіосах" повинно бути мало "а".
bigp

Я взяв Axois як const, що імпортує з "axios"
Bhaskararao Gummidi

0

TL: DR;повертайте обіцянки від вас лише тоді, коли це необхідно, але DRY прикуває ті самі дії.

Давно я також вважаю, що дії, що повертаються, суперечать циклу Vuex однонаправленого потоку даних.

Але, є випадки EDGE коли повернення обіцянки від ваших дій може бути "необхідним".

Уявіть ситуацію, коли дія може бути спровокована з двох різних компонентів, і кожен обробляє випадок відмови по-різному. У цьому випадку потрібно буде передавати компонент виклику як параметр для встановлення різних прапорів у магазині.

Тупий приклад

Сторінка, на якій користувач може редагувати ім’я користувача в navbar і на / сторінці профілю (яка містить вказівник). Обидва запускають дію "змінити ім'я користувача", яка є асинхронною. Якщо обіцянка не виконана, сторінка повинна відображати лише помилку в компоненті, з якого користувач намагався змінити ім'я користувача.

Звичайно, це німий приклад, але я не бачу способу вирішити цю проблему без дублювання коду та здійснення одного і того ж дзвінка у двох різних діях.


-1

Actions.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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.