После частого использования Redux и Vuex я начинаю замечать появление некоторых закономерностей. Вот один, который я заметил в своих приложениях Vuex, и как я извлек его в несколько служебных функций. Ниже приводится действие, которое я пишу слишком часто:

const actions = {
  fetchApiData (store) {  
    // sets `state.loading` to true. Show a spinner or something.
    store.commit('API_DATA_PENDING') 
    
    return axios.get('someExternalService')
    .then(response => {       
      // sets `state.loading` to false 
      // also sets `state.apiData to response`
      store.commit('API_DATA_SUCCESS', response.data) 
    })
    .catch(error => { 
      // set `state.loading` to false and do something with error   
 
      store.commit('API_DATA_FAILURE', error)
    })
  }
}

Написание этого для каждого вызова ajax было повторяющимся и делало мои действия очень долгими.

Я намеревался реализовать что-то, что дало бы мне следующее:

  • Обработка всех трех состояний запроса ajax - успеха, сбоя и ожидания.
  • Уметь легко создавать новые действия, используя как можно меньше шаблонов.
  • Возможность многократного использования - почти все SPA будут использовать такую ​​функциональность.

Три состояния: успех, неудача и ожидание

Первым шагом является реализация функции, которая будет генерировать следующее для мутации под названием GET_INFO_ASYNC:

const GET_INFO_ASYNC = {
  SUCCESS: 'GET_INFO_ASYNC_SUCCESS',
  FAILURE: 'GET_INFO_ASYNC_FAILURE',
  PENDING: 'GET_INFO_ASYNC_PENDING', 
  loadingKey: getInfoAsyncPending,
  dataKey: getInfoAsyncData
}

Следующая функция делает это для любого заданного type. Я также включаю loadingKey и dataKey - в состоянии моего магазина статус ожидания будет сохранен в loadingKey, а ответ - в dataKey.

// src/mutation-types.js
import _ from 'lodash'
const createAsyncMutation = (type) => ({
  SUCCESS: `${type}_SUCCESS`,
  FAILURE: `${type}_FAILURE`,
  PENDING: `${type}_PENDING`,
  loadingKey: _.camelCase(`${type}_PENDING`),
  stateKey: _.camelCase(`${type}_DATA`)
})

Это позволяет нам создать все три мутации, просто вызвав createAsyncMutation(‘GET_INFO’).

Затем еще один метод обработки вызова ajax и мутаций для нас.

// src/async-util.js
import axios from 'axios'
const doAsync = (store, { url, mutationTypes }) => {
  store.commit(mutationTypes.PENDING)
  axios(url)
    .then(response => {
      store.commit(mutationTypes.SUCCESS, response.data)
    })
    .catch(error => {
      store.commit(mutationTypes.FAILURE)
    })
}
export default doAsync

Мы просто передаем магазин, URL-адрес службы, к которой мы обращаемся, и mutation-types, созданный с помощью createAsyncMutation. Это решает проблему шаблонного действия. Мы можем использовать указанную выше утилиту для создания таких действий:

const actions = {
  getInfoAsync(store) {
    doAsync(store, {
      url: 'https://jsonplaceholder.typicode.com/posts/1',
        mutationTypes: types.GET_INFO_ASYNC
      })
    },
  }
}

Последнее, что нужно сделать, это создать мутации:

const mutations = {
  [types.GET_INFO_ASYNC.SUCCESS] (state, data) {
    state[types.GET_INFO_ASYNC.loadingKey] = false
    Vue.set(state, [types.GET_INFO_ASYNC.dataKey], data)
  },
  [types.GET_INFO_ASYNC.PENDING] (state) {
    Vue.set(state, types.GET_INFO_ASYNC.loadingKey, true)
  }
}

Это обрабатывает loadingKey, а также устанавливает данные. Обратите внимание, что мы используем Vue.set, простое выполнение state[types.GET_INFO_ASYNC.loadingKey] не будет работать с системой реактивности Vue - свойства должны быть созданы при начальной загрузке или Vue.set, чтобы они были реактивными.

Все еще существует небольшой шаблон, связанный с вышеупомянутыми мутациями. Однако я иногда обрабатываю данные в мутациях - относится ли это к действиям или нет, зависит от ряда факторов. Примером может быть toggleTodomutation, где вы находите todo, используя полезную нагрузку из внешнего API, а затем переключаете done на false или что-то в этом роде.

Приведенный выше код может использоваться приложением следующим образом:

// App.vue
<template>
  <div id="app">
  <p>
   Pending: {{ $store.state.getInfoAsyncPending }}
  </p>
  <p>
    {{ $store.state.getInfoAsyncData }}
  </p>
  </div>
</template>
<script>
export default {
  created () {
    this.$store.dispatch('getInfoAsync')
  }
}
</script>

Следующее, что я хотел бы сделать, это извлечь это в плагин, чтобы сделать его еще более гибким и многоразовым. Может следующий пост!

Суть исходного кода здесь. См. Файл main.js для получения инструкций по настройке демонстрации или отправьте сообщение, могу ли я загрузить репо :)

Мне интересно услышать, как другие люди используют разные шаблоны или методы, чтобы сделать действия более лаконичными и уменьшить шаблон, связанный с ними, поэтому, пожалуйста, оставьте комментарий!