Введение в Vuex: управление состоянием Vue.js

управление состоянием Vue.js

Если вы когда-нибудь всерьез занимались разработкой крупномасштабных одностраничных приложений, вы, вероятно, знакомы с таким понятием, как управление состоянием (в том числе с архитектурой Flux, популяризированной Facebook через Redux). В данном мануале мы покажем, как с помощью Vuex реализовать подобный шаблон в приложении Vue.

Установка Vuex

Vuex является официальным пакетом для Vue, но он не встроен по умолчанию. Вы должны сами решить, понадобится ли он вашему приложению, а затем установить его соответственно через менеджер yarn или npm.

# Yarn

$ yarn add vuex

# NPM

$ npm install vuex --save

Затем нужно включить плагин Vuex в начальной загрузке приложения.

import Vue from 'vue';

import Vuex from 'vuex';

import App from 'App.vue';

Vue.use(Vuex);

new Vue({

  el: '#app',

  render: h => h(App)

});

Установка Vuex завершена.

Создание хранилища данных

Чтобы воспользоваться преимуществами, которые предоставляет Vuex, нужно создать хранилище данных. Это, по сути, глобальный реактивный объект, который следует обычным шаблонам реактивности Vue. К нему нельзя получить прямой доступ или напрямую изменить его, чтобы обеспечить согласованное состояние и упростить отслеживание изменений. Чтобы создать хранилище, поместите в файл store.js такие строки:

export const store = new Vuex.Store({

  state: {

    safelyStoredNumber: 0

  }

});

Чтобы получить доступ к хранилищу, нужно будет импортировать его во все компоненты приложения, либо внедрить его в корневой экземпляр Vue, чтобы он автоматически импортировался во все остальные компоненты как this.$store. Здесь мы будем использовать последний метод. Откройте main.js и поместите в него следующий фрагмент:

import Vue from 'vue';

import Vuex from 'vuex';

import App from 'App.vue';

import { store } from './store.js';

Vue.use(Vuex);

new Vue({

  store,

  el: '#app',

  render: h => h(App)

});

Доступ к состоянию

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

Использование геттеров

Геттер – это просто функция в хранилище, которая принимает объект состояния и возвращает его значение. В ваших компонентах доступ к данным можно получить через this.$store.getters.property (как через вычисляемое свойство, а не как через функцию). Если геттеру нужен параметр, он может вернуть вторую функцию, которая примет необходимый параметр.

store.js

export const store = new Vuex.Store({

  state: {

    safelyStoredNumber: 0

  },

  getters: {

    safelyStoredNumber: state => state.safelyStoredNumber,

    storedNumberMatches(state) {

      return matchNumber => {

          return state.safelyStoredNumber === matchNumber;

      }

    }

    // краткая форма:

    // storedNumberMatches: state => matchNumber => state.safelyStoredNumbers === matchNumber

  }

});

Однако существует более простой способ получить доступ к геттерам в компоненте – для этого нужно использовать вспомогательный метод Vuex под названием mapGetters. Это позволяет монтировать геттеры к вычисляемым свойствам верхнего уровня.

Метод mapGetters может принимать объект, если вы хотите переименовать геттеры в своем компоненте.

App.vue

<template>

  <p>The safely stored number: {{safelyStoredNumber}}<p>

</template>

<script>

import { mapGetters } from 'vuex'

export default {

  computed: {

    ...mapGetters([

      // монтирует геттер "safelyStoredNumber" в диапазон компонента.

      'safelyStoredNumber'

    ])

  }

}

</script>

Изменение состояния

Синхронные мутации

Прямое изменение состояния в Vuex выполняется путем вызова функции, называемой мутацией. Мутация принимает текущее состояние и (опционально) полезную нагрузку. Полезной нагрузкой может быть любой объект. Мутации должны быть синхронными и не должны возвращать никаких значений. Их можно использовать напрямую в формате this.$store.commit(‘mutationName’, payload).

store.js

export const store = new Vuex.Store({

  state: {

    safelyStoredNumber: 0

  },

  ...

  mutations: {

    incrementStoredNumber(state) {

      state.safelyStoredNumber++;

    },

    setStoredNumber(state, newNumber) {

      // newNumber - это передаваемая полезная нагрузка.

      state.safelyStoredNumber = newNumber;

    }

  }

});

Как и в случае с геттерами, Vuex предоставляет удобный метод для использования мутаций в компоненте, это вспомогательный метод mapMutations. Он позволяет монтировать мутации как методы.

App.vue

<template>

  <p>The safely stored number: {{safelyStoredNumber}}<p>

</template>

<script>

import { mapMutations } from 'vuex'

export default {

  ...

  methods: {

    ...mapMutations([

      // монтирует мутацию "incrementStoredNumber" в `this.incrementStoredNumber()`.

      'incrementStoredNumber',

      // монтирует мутацию "setStoredNumber" в `this.setStoredNumber(newNumber)`.

      'setStoredNumber'

    ])

  }

}

</script>

Асинхронные действия

В более сложных приложениях вам, вероятно, нужно выполнять некоторые асинхронные действия для изменения состояния. Vuex выполняет этй задачу с помощью так называемых действий, или экшенов (action). Они также определены в объекте состояния и передают весь контекст, что позволяет им получать доступ к геттерам и фиксировать мутации. Обычно (но не всегда) они возвращают промис, указывающий на статус выполнения. Используя функции ES2017 async/await, вы можете писать очень короткие и простые асинхронные экшены. Они используются в компонентах напрямую в формате this.$store.dispatch(‘actionName’, payload).then(response => {}).

Чтобы изменить состояние внутри экшена, используйте context.commit(‘mutationName’, payload). Внутри экшена может находиться несколько мутаций.

store.js

import myRemoteService from './my-remote-service.js'

export const store = new Vuex.Store({

  state: {

    safelyStoredNumber: 0

  },

  ...

  actions: {

    async setNumberToRemoteValue(context) {

      // регистрирует мутацию 'setStoredNumber' со значением, с которым myRemoteService.getRemoteValue() разрешается через промис.

      context.commit('setStoredNumber', await myRemoteService.getRemoteValue());

      return Promise.resolve();

    },

  }

});

Если вы не знакомы с async/await, прочтите наше руководство Функции async/await в JavaScript. Если вкратце, они приостанавливают выполнение текущей функции до тех пор, пока не разрешится ожидаемый промис, что позволяет использовать промисы в качестве переменных.

Удобный метод Vuex для экшенов (который предсказуемо называется mapActions) используется так же, как и аналогичный метод для мутаций.

App.vue

<template>

  <p>The safely stored number: {{safelyStoredNumber}}<p>

</template>

<script>

import { mapActions } from 'vuex'

export default {

  ...

  methods: {

    ...mapActions([

      // монтирует экшен "setNumberToRemoteValue" в `this.setNumberToRemoteValue()`.

      'setNumberToRemoteValue',

    ])

  }

}

</script>

Модуляризация Vuex

Одного хранилища хватит, если вы работаете с небольшим набором данных. Но в работе неизбежно наступает момент, когда вам хочется разделить постоянно растущий список экшенов, мутаций и геттеров на отдельные разделы. К счастью, Vuex предоставляет для этого специальную систему – модули. Модуль – это обычный объект со свойствами состояния, геттеров, мутаций и экшенов. Вы можете легко создать его, выполнив следующее:

my-store-module.js

export const myModule = {

  // делает ваши геттеры, мутации и экшены доступными, например: 'myModule/myModularizedNumber' используется вместо монтирования геттеров, мутаций и экшенов в корневом пространстве имен.

  namespaced: true,

  state: {

    myModularizedNumber: 0

  },

  getters: {

    myModularizedNumber: state => state.myModularizedNumber

  },

  mutations: {

    setModularizedNumber(state, newNumber) {

      state.myModularizedNumber = newNumber

    }

  }

}

store.js

import { myModule } from './my-store-module.js';

export const store = new Vuex.Store({

  modules: {

      myModule

  },

  state: {

    safelyStoredNumber: 0

  },

  ...

});

Модули можно вкладывать в другие модули, количество уровней вложения не ограничено. Кроме того, методы mapGetters, mapMutations и mapActions могут принимать пространство имен модуля в качестве первого аргумента, чтобы вам не пришлось писать такой код:

...mapGetters([

  'myModule/nestedModule/subNestedModule/exampleGetter',

  'myModule/nestedModule/subNestedModule/anotherGetter',

])

Вы можете написать то же самое намного проще:

...mapGetters('myModule/testedModule/subNestedModule', [

  'exampleGetter',

  'anotherGetter'

])

Надеемся, это руководство помогло вам разобраться с управлением состоянием приложения с помощью Vuex.

Читайте также: Динамическая загрузка модулей Vuex

Tags: ,

Добавить комментарий