Для начала, что бы понять для чего и как работает vuex создадим приложение, которое для обмена данными между компонентами использует props и emit.
main.js
/* jshint ignore:start */
import { createApp } from "vue"
import App from "./App.vue"
const app = createApp(App)
app.mount("#app")
App.vue
<template>
<div>
<div class="container text-center pt-5">
<p>App</p>
</div>
<app-counter :counter="counter"></app-counter>
<app-action @counterUpdated="counter += $event"></app-action>
</div>
</template>
<script>
import Counter from "./Counter.vue"
import Action from "./Action.vue"
export default {
components: {
appCounter: Counter,
appAction: Action
},
data () {
return {
counter: 0
}
}
}
</script>
<style scoped></style>
Counter.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Counter</p>
<h2>Counter {{ counter }}</h2>
</div>
</div>
</template>
<script>
export default {
props: ["counter"]
}
</script>
<style scoped></style>
Action.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Action</p>
<button class="btn btn-success" @click="updateCounter(1)">Add</button>
<button class="btn btn-danger" @click="updateCounter(-1)">Subtract</button>
</div>
</div>
</template>
<script>
export default {
methods: {
updateCounter(val) {
this.$emit("counterUpdated", val)
}
}
}
</script>
<style scoped></style>
Что происходит в этом приложении, при нажатии кнопок наш счетчик прибавляет единицу или вычитает. Обмен данными в этом случае проходит с помощью props и emit через главный компонент.
Документация vuex и сайт npm vuex.
Для установки через npm воспользуемся командой - npm install vuex@next --save.
После этого в разделе "dependencies" package.json появятся строка ""vuex": "^4.0.2"".
Рассмотрим как осуществляется переход от props и emit к vuex.
В проекте в папке "src" необходимо создать папку "store" и в ней файл с наименование нашего "хранилища данных", в данном случае назовем его index.js.
В store index.js пишем код, который подключает vuex и готовим конструкцию плагина.
index.js
/* jshint ignore:start */
import { createStore } from "vuex"
const store = createStore({
state () {
return {
counter: 1
}
}
})
export default store
Далее подключаем store vuex у нашему проекту.
main.js
/* jshint ignore:start */
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store/index.js"
const app = createApp(App)
app.use(store)
app.mount("#app")
В App.vue мы избавляемся от данных data - counter, и избавляемся от параметров передачи компонентов.
App.vue
<template>
<div>
<div class="container text-center pt-5">
<p>App</p>
</div>
<app-counter></app-counter>
<app-action></app-action>
</div>
</template>
<script>
import Counter from "./Counter.vue"
import Action from "./Action.vue"
export default {
components: {
appCounter: Counter,
appAction: Action
},
// data () {
// return {
// counter: 0
// }
// }
}
</script>
<style scoped></style>
В Counter.vue отказываемся от props в пользу vuex.
Counter.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Counter</p>
<h2>Counter {{ counter }}</h2>
</div>
</div>
</template>
<script>
export default {
//props: ["counter"]
computed: {
counter() {
return this.$store.state.counter
}
}
}
</script>
<style scoped></style>
В Action.vue отказываемся от emit в пользу vuex.
Action.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Action</p>
<button class="btn btn-success" @click="updateCounter(1)">Add</button>
<button class="btn btn-danger" @click="updateCounter(-1)">Subtract</button>
</div>
</div>
</template>
<script>
export default {
methods: {
updateCounter(val) {
//this.$emit("counterUpdated", val)
this.$store.state.counter += val
}
}
}
</script>
<style scoped></style>
Getters позволяют внутри store делать дополнительные вычисления и обработку данных. Рассмотрим на примере использование getters.
index.js
/* jshint ignore:start */
import { createStore } from "vuex"
const store = createStore({
state () {
return {
counter: 0
}
},
getters: {
computedCounter(state) {
return state.counter * 10
}
}
})
export default store
main.js
/* jshint ignore:start */
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store/index.js"
const app = createApp(App)
app.use(store)
app.mount("#app")
App.vue
<template>
<div>
<div class="container text-center pt-5">
<p>App</p>
</div>
<app-counter></app-counter>
<app-action></app-action>
</div>
</template>
<script>
import Counter from "./Counter.vue"
import Action from "./Action.vue"
export default {
components: {
appCounter: Counter,
appAction: Action
},
}
</script>
<style scoped></style>
Counter.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Counter</p>
<h2>Counter {{ counter }}</h2>
</div>
</div>
</template>
<script>
export default {
computed: {
counter() {
return this.$store.getters.computedCounter
}
}
}
</script>
<style scoped></style>
Action.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Action</p>
<button class="btn btn-success" @click="updateCounter(1)">Add</button>
<button class="btn btn-danger" @click="updateCounter(-1)">Subtract</button>
</div>
</div>
</template>
<script>
export default {
methods: {
updateCounter(val) {
this.$store.state.counter += val
}
}
}
</script>
<style scoped></style>
Рассмотрим на примере для чего могут быть использованы mutations.
В Action.vue откажемся от "this.$store.state.counter += val" в пользу использования mutations.
index.js
/* jshint ignore:start */
import { createStore } from "vuex"
const store = createStore({
state () {
return {
counter: 0
}
},
mutations: {
changeCounter(state, payload) {
state.counter += payload
}
},
getters: {
computedCounter(state) {
return state.counter * 10
}
}
})
export default store
Action.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Action</p>
<button class="btn btn-success" @click="updateCounter(1)">Add</button>
<button class="btn btn-danger" @click="updateCounter(-1)">Subtract</button>
</div>
</div>
</template>
<script>
export default {
methods: {
updateCounter(val) {
//this.$store.state.counter += val
this.$store.commit("changeCounter", val)
}
}
}
</script>
<style scoped></style>
Рассмотрим на примере использование actions для осуществления паузы при нажатии на кнопки.
index.js
/* jshint ignore:start */
import { createStore } from "vuex"
const store = createStore({
state () {
return {
counter: 0
}
},
mutations: {
changeCounter(state, payload) {
state.counter += payload
}
},
actions: {
asyncChangeCounter(context, payload) {
setTimeout(() => {
context.commit("changeCounter", payload)
}, 1000)
}
},
getters: {
computedCounter(state) {
return state.counter * 10
}
}
})
export default store
Action.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Action</p>
<button class="btn btn-success" @click="updateCounter(1)">Add</button>
<button class="btn btn-danger" @click="updateCounter(-1)">Subtract</button>
</div>
</div>
</template>
<script>
export default {
methods: {
updateCounter(val) {
//this.$store.commit("changeCounter", val)
this.$store.dispatch("asyncChangeCounter", val)
}
}
}
</script>
<style scoped></style>
Для передачи параметров мы используем определенное написание ключей, как в примере ниже.
index.js
/* jshint ignore:start */
import { createStore } from "vuex"
const store = createStore({
state () {
return {
counter: 0
}
},
mutations: {
changeCounter(state, payload) {
state.counter += payload
}
},
actions: {
asyncChangeCounter(context, payload) {
setTimeout(() => {
context.commit("changeCounter", payload.counterValue)
}, payload.timeoutDelay)
}
},
getters: {
computedCounter(state) {
return state.counter * 10
}
}
})
export default store
Action.vue
<template>
<div>
<div class="container text-center pt-5">
<p>Action</p>
<button class="btn btn-success" @click="updateCounter(1)">Add</button>
<button class="btn btn-danger" @click="updateCounter(-1)">Subtract</button>
</div>
</div>
</template>
<script>
export default {
methods: {
updateCounter(val) {
//this.$store.commit("changeCounter", val)
this.$store.dispatch("asyncChangeCounter", {
counterValue: val,
timeoutDelay: 1000
})
}
}
}
</script>
<style scoped></style>
Чаще всего action описывается вот так:
Пример:
actions: {
asyncChangeCounter({commit}, payload) {
setTimeout(() => {
commit("changeCounter", payload.counterValue)
}, payload.timeoutDelay)
}
},
Vuex позволяет разбивать store на множество модулей для удобства. Как это делается показано в простом примере ниже.
Для каждого модуля создаем отдельный файл в папке "store" с его параметрами store, а в основном файле store - index.js подключаем эти модули.
index.js
/* jshint ignore:start */
import { createStore } from "vuex"
import counter from "./counter.js"
const store = createStore({
modules: {
counter: counter
}
})
export default store
counter.js
/* jshint ignore:start */
export default {
state () {
return {
counter: 0
}
},
mutations: {
changeCounter(state, payload) {
state.counter += payload
}
},
actions: {
asyncChangeCounter(context, payload) {
setTimeout(() => {
context.commit("changeCounter", payload.counterValue)
}, payload.timeoutDelay)
}
},
getters: {
computedCounter(state) {
return state.counter * 10
}
}
}