Vuex использует единое дерево состояния — таким образом, один-единственный объект содержит всё глобальное состояние приложения и служит "единственным источником истины". Кроме того, это значит, что обычно для каждого приложения существует только одно хранилище. Благодаря единому дереву можно легко найти нужную часть состояния или делать слепки всего состояния приложения для отладки.
Единое дерево состояния не конфликтует с концепцией модульности — в последующих главах мы расскажем, как можно разбить хранилище на подмодули.
Так как же отобразить состояние хранилища в компонентах Vue? Поскольку хранилище Vuex реактивно, проще всего использовать вычисляемые свойства:
// создадим компонент-счётчик:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
Любые изменения store.state.count
вызовут обновление вычисляемого свойства, которые инициируют соответствующие обновления в DOM.
Однако этот паттерн заставляет компонент явно полагаться на глобальный синглтон хранилища. При использовании модульной системы, это потребует импортирования хранилища в каждый компонент, использующий глобальное состояние и приведет к усложнению тестирования.
Vuex предоставляет механизм "инъекции" хранилища всем потомкам компонента, у которого указана опция store
(предварительно необходимо вызвать Vue.use(Vuex)
):
const app = new Vue({
el: '#app',
// указываем хранилище в опции "store", что обеспечит
// доступ к нему также и для всех дочерних компонентов
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
Указывая опцию store
для корневого экземпляра, мы обеспечиваем доступ к хранилищу всем дочерним компонентам в this.$store
. Давайте обновим наш пример со счётчиком:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState
Если компонент использует множество свойств или геттеров хранилища, объявление доступа к ним всем вручную может заставить изрядно заскучать, да и код получится многословный. Чтобы обойти эту проблему, можно использовать хелпер mapState
, автоматически генерирующий вычисляемые свойства, проксирующие доступ к состоянию и геттерам хранилища:
// с полной сборкой можно использовать как Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// стрелочные функции позволяют писать код очень лаконично
count: state => state.count,
// передача строки 'count' эквивалентна записи `state => state.count`
countAlias: 'count',
// если требуется доступ и к локальному состоянию, нужно использовать традиционную функцию
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
В простых случаях в mapState
можно передать и просто массив строк:
computed: mapState([
// проксирует через this.count доступ к store.state.count
'count'
])
Обратите внимание: mapState
возвращает объект. Как же быть, если нам нужны и локальные вычисляемые свойства? Обычно в таких случаях приходилось использовать вспомогательные утилиты для слияния объектов, и передавать уже результат их работы в computed
. Однако, применив оператор распространения объектов (находящегося в статусе stage-3 ECMAScript proposal) мы можем изрядно упростить запись:
computed: {
localComputed () { /* ... */ },
// результаты работы mapState будут добавлены в уже существующий объект
...mapState({
// ...
})
}
То что вы используете Vuex не значит, что нужно выносить в хранилище всё состояние приложения. Поместив большую часть логики во Vuex, вы сделаете мутации более красноречивыми и удобными для отладки, но это иногда может привести к излишней многословности и ненужному усложнению логики. Если состояние компонента полностью локально, выносить его во Vuex может быть бессмысленно. Конечное решение всегда остаётся на усмотрение разработчика и зависит от потребностей конкретного приложения.