Vuex adalah state management, seperti Redux tapi lebih mudah, layaknya data local di browser, sehingga memudahkan untuk transfer data antar component
Bila kita tidak menggunakan Vuex, untuk transfer data dari parent ke child harus menggunakan prop, dan sebaliknya harus trigger event dari child to parent, Super Ribett
Bila kita sudah menggunakan Vuex, komunikasi antar component menjadi sgt mudah, dari c1 tinggal mutation data ke store, dan dari c2 tinggal getter data dari store
Untuk memulai sample Vuex kita harus setting enviroment Install vue cli
npm install -g vue-cli
Buat project vuex
vue init webpack-simple vuex
Install package
npm install
Jalankan Server
npm run dev
Pada tahap ini kita masih menggunakan tehnik/cara lama tanpa vuex
Pada kasus di bawah terdapat data array buah buahan yang terletak di bagian root App, data array ini akan di render di 2 component yang berbeda
Cara lama di lakukan dengan mem-passing array buah-buahan melalui props, dimana masing-masing component (ProductOne dan ProductTwo) menerima props tersebut dan me-rendernya
ProductOne.vue
<template>
<div>
<h1>Buah-Buahan One</h1>
<ul>
<li v-for="product in products" :key="product.name">
<p>{{ product.name }}, Harga Rp.{{ product.price }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
products: {
type: Array,
required: true,
default: []
}
}
};
</script>
<style scoped>
p {
color: green;
}
</style>
ProductTwo.vue
<template>
<div>
<h1>Buah-Buahan Two</h1>
<ul>
<li v-for="product in products" :key="product.name">
<p>{{ product.name }}, Harga Rp.{{ product.price }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
products: {
type: Array,
required: true,
default: []
}
}
};
</script>
<style scoped>
p {
color: red;
}
</style>
App.vue
<template>
<div>
<product-one :products="products"></product-one>
<product-two :products="products"></product-two>
</div>
</template>
<script>
import ProductOne from "./components/ProductOne.vue";
import ProductTwo from "./components/ProductTwo.vue";
export default {
name: "app",
components: {
"product-one": ProductOne,
"product-two": ProductTwo
},
data() {
return {
products: [
{ name: "Mangga", price: 20000 },
{ name: "Semangka", price: 15000 },
{ name: "Jambu", price: 18000 }
]
};
}
};
</script>
Pada tahap awal, untuk menggunakan vuex, kita harus melakukan setup Store
Store adalah object placeholder, tempat menyimpan application data
src/store/store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
products: [
{ name: "Mangga", price: 20000 },
{ name: "Semangka", price: 15000 },
{ name: "Jambu", price: 18000 }
]
}
});
Store yang telah kita setup pada tutorial sebelumnya, siap untuk di gunakan
import store dari file store.js dan pasang pada Vue Instance, agar dapat di gunakan di mana saja melalui this.$store
Untuk mengambil data dari store, kita menggunakan computed, dimana store root object dapat di access melalui this.$store.state...
main.js
import Vue from "vue";
import App from "./App.vue";
import { store } from "./store/store";
new Vue({
store, // equal store: store
el: "#app",
render: h => h(App)
});
ProductOne.vue
<template>
<div>
<h1>Buah-Buahan One</h1>
<ul>
<li v-for="product in products" :key="product.name">
<p>{{ product.name }}, Harga Rp.{{ product.price }}</p>
</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
products() {
return this.$store.state.products;
}
}
};
</script>
<style scoped>
p {
color: green;
}
</style>
Sebenarnya kita dapat meng-access state dalam store secara langsung, layaknya this.$store.state.products
Namun ada kalanya kita mau meng-access data tersebut yang telah di manipulasi tanpa mengubah data original, untuk hal ini kita menggunakan getters
Untuk membuat getters kita menambahkannya pada block getters, kemudian menambahkan function yang selalu menerima state sebagai argument
Untuk memanggil getters kita menggunaan statement this.$store.getters.{name function}
store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
products: [
{ name: "Mangga", price: 20000 },
{ name: "Semangka", price: 15000 },
{ name: "Jambu", price: 18000 }
]
},
getters: {
salesProduct(state) {
return state.products.map(p => {
return { name: "**" + p.name + "**", price: p.price / 2 };
});
}
}
});
<script>
export default {
computed: {
products() {
return this.$store.state.products;
},
salesProduct() {
return this.$store.getters.salesProduct;
}
}
};
</script>
Untuk melakukan manipulasi data original yang ada di Store kita menggunakan mutations
Dilarang keras untuk melakukan perubahan data original secara langsung, karena akan menyulitakan tracing siapa yang melakukan perubahan (gunakanlah mutations)
Sebenarnya Vuex dapat mencegah perubahan data secara langsung dengan menggunakan strict:true
Karena code perubahan data original terdapat di store muataions, maka coding di sisi component hanya berupa commit dan transfer argument saja
this.$store.commit("reducePrice", 1000);
memiliki arti, lakukan mutation reducePrice dengan mengirim argument 1000
store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
strict: true,
state: {
products: [
{ name: "Mangga", price: 20000 },
{ name: "Semangka", price: 15000 },
{ name: "Jambu", price: 18000 }
]
},
getters: {
salesProduct(state) {
return state.products.map(p => {
return { name: "**" + p.name + "**", price: p.price / 2 };
});
}
},
mutations: {
reducePrice(state, reduceBy) {
state.products.forEach(p => {
p.price -= reduceBy;
});
}
}
});
ProductOne.vue
methods: {
reducePrice() {
this.$store.commit("reducePrice", 1000);
}
}
Untuk ambil store menggunakan getters, untuk mengubah store menggunakan mutations, seharusnya sudah ok dong?.... Salah
Kekurangan dari mutation adalah bila ada oprasional asynch, mutation akan selalu di jalankan langusng, lalu setelah selesai asynch baru update ui (VueTool)
Untuk best practice ada baiknya menggunakan Actions, bila kita menggunakan action oprational mutation hanya dilakukan setelah update ui selesai (VueTool)
Untuk menipulasi data tetap dilakukan di bagian mutation, Actions hanya sebagai perantara yang meng-commit mutations
Berbeda dgn getter dan mutation yg menerima state sbg argument, actions menerima context sebagai argument, contex == $store
Untuk memanggil action kita menggunakan dispatch this.$store.dispatch("reducePrice", amount);
store.js
import Vue from "vue";
import Vuex from "vuex";
import { isContext } from "vm";
Vue.use(Vuex);
export const store = new Vuex.Store({
strict: true,
state: {
products: [
{ name: "Mangga", price: 20000 },
{ name: "Semangka", price: 15000 },
{ name: "Jambu", price: 18000 }
]
},
getters: {
salesProduct(state) {
return state.products.map(p => {
return { name: "**" + p.name + "**", price: p.price / 2 };
});
}
},
mutations: {
reducePrice(state, payload) {
state.products.forEach(p => {
p.price -= payload;
});
}
},
actions: {
reducePrice(contex, payload) {
setTimeout(function() {
contex.commit("reducePrice", payload);
}, 3000);
}
}
});
Product.vue
methods: {
reducePrice(amount) {
this.$store.dispatch("reducePrice", amount);
}
}
Anggap dalam store.js memiliki 20 getter dan 20 actions, maka untuk component yang menggunakan store ini, perlu membuat 20 computed dan 20 method
Hal ini di anggap pemborosan dan redudansi coding. Oleh sebab itu vue telah menyediakan mapping untuk getters dan actions, caranya adalah ...
Lakukan import mapGetter dan mapActions dari vuex
Lakukan sprade operator di bagian computed untuk mapGetters
Lakukan sprade operator di bagian methods untuk mapActions
<script>
import { mapGetters } from "vuex";
import { mapActions } from "vuex";
export default {
computed: {
products() {
return this.$store.state.products;
},
...mapGetters(["salesProduct"])
},
methods: {
...mapActions(["reducePrice"])
}
};
</script>