Trong một ứng dụng lớn nói chung, ứng dụng Vue nói riêng, việc các components phải dùng chung state là điều khó tránh khỏi. Để thực hiện điều đó, bạn sẽ phải truyền biến đó qua lại giữa các component. Việc này làm cho mã nguồn trở lên lộn xộn, khó đọc. Đây là lúc bạn nghĩ tới sử dụng Vuex để quản lý State.
Mình lấy một ví dụ điển hình cho bạn dễ hình dung: Ứng dụng có tính năng login, do vậy trạng thái (state) người dùng đã đăng nhập/đăng xuất sẽ được sử dụng ở mọi component trong ứng dụng. Thay vì state này được truyền “tùm lum” mỗi khi cần đến, bạn sẽ quản lý state đó ở một nơi duy nhất. Tất cả các component nếu cần thì vào đó mà lấy.
Có một thư viện chuyên quản lý state đó là Vuex. Bài viết này chúng ta cùng tìm hiểu cách sử dụng Vuex để quản lý state trong ứng dụng VueJS nhé.
Nội dung chính của bài viết
Vuex là gì?
Vuex là thư viện chuyên quản lý State cho ứng dụng VueJS. Nếu bạn đã từng làm việc với React thì có lẽ Vuex cũng có chức năng tương tự như Redux.
Vuex sẽ lưu tất cả State của ứng dụng vào một nơi gọi Store.
Khi sử dụng Vuex, mọi component sẽ sử dụng cùng một State. Nếu không, mỗi component sẽ có một phiên bản riêng của State đó.
Phần tiếp theo của bài viết, mình sẽ hướng dẫn:
- Cách cài đặt Vuex store.
- Cách sử dụng Store này trong các Components.
Chúng ta sẽ bắt đầu bằng việc khởi tạo một dự án Vue mới. Đọc lại cách tạo dự án Vue.
Cài đặt Vuex
Cách cài đặt Vuex cũng tương tự như mọi module khác thôi.
npm install vuex --save
Hoặc
yarn add vuex
Sử dụng Vuex khởi tạo Store
Một store có thể có nhiều modules, và để cấu trúc mã nguồn dự án đỡ lộn xộn, mình sẽ tạo riêng một thư mục store
bên trong thư mục src
.
Tiếp tục tạo một thư mục “modules
” bên trong store. Cuối cùng là tạo tệp index.js
bên trong thư mục store
. Kết quả sẽ có cây thư mục như hình dưới đây.
Store index
Giả sử chúng ta có module là products, nơi chứa state của các product. Vì vậy, trong index.js
, chúng ta sẽ cần khai báo vào import những module store được sử dụng.
import Vue from 'vue'; import Vuex from 'vuex'; import {products} from './modules/products'; Vue.use(Vuex); export const store = new Vuex.Store({ modules: { products, } });
Modules
Tất cả State của ứng dụng Vue sẽ được lưu trong một đối tượng. Đối tượng này có sẽ phình to ra khi ứng dụng ngày càng phức tạp lên.
May mắn là Vuex cho phép bạn chia nhỏ store thành nhiều modules. Mỗi modules sẽ độc lập với nhau, và có đầy đủ 4 thành phần: state, getters, actions và mutations
Vì vậy, khi bạn tạo mới một module, bạn cần tạo những files sau:
index.js
getters.js
actions.js
mutations.js
Mình sẽ giải thích và cùng thực hành viết code chi tiết cho từng file một.
1. index.js
index.js
là nơi mình định nghĩa state của các modules. Hơn nữa, tệp này sẽ đóng gói getters, actions, mutations thành một object đại diện cho state đó.
Mặc định, mọi thứ bên trong một module sẽ được đăng ký hoạt động cho phạm vi toàn dự án (global). Điều này cho phép nhiều modules có thể phản ứng với cùng một mutation hay action.
Tuy nhiên, mình không thích cách mặc định này, mình thích các module nên đóng kín và hoạt động riêng rẽ thay vì global. Để làm điều này mình đơn giản đặt namespaced = true
trong các module.
import {getters} from './getters' import {actions} from './actions' import {mutations} from './mutations' const state = { products: [], status: null, }; const namespaced = true; export const products = { namespaced, state, getters, actions, mutations };
Như bạn có thể thấy ở đoạn code trên, State chỉ là một đối tượng với các thuộc tính là các modules.
2. mutations.js
Như cái tên của nó, mutations.js
sẽ chứa tất cả các hàm mutation.
Theo định nghĩa, Mutations là các hàm dùng để thay đổi giá trị của state.
Trong Vuex, đối số đầu tiên của hàm mutation luôn là đối tượng state.
export const mutations = { setProducts(state, products) { Vue.set(state, 'products', products); }, addProduct(state, product) { let products = state.products; products.push(product); Vue.set(state, 'products', products); }, };
3. actions.js
Tệp actions.js
chứa tất cả các hàm action. Các hàm actions gần giống với hàm mutation, nhưng chúng không làm thay đổi state. Đặc biệt, các hàm action có thể làm việc bất đồng bộ (asynchronous).
export const actions = { getProducts(context) { this.axios.get('/api/products') .then((response) => { let products = response.data.data; context.commit('setProducts', products); }); }, addProduct(context, product) { context.commit('addProduct', product); } };
Trong ví dụ trên, chúng ta có hai action, cả hai action này đều commit một mutation. Trong đó, mình có minh họa cả hai kiểu: hàm action chạy động bộ (hàm addProduct
) và chạy không đồng bộ (hàm getProducts
).
4. getters.js
Tệp getters.js
chứa tất cả các hàm getter. Hàm getter là một store cho những tính toán các properties của component.
Hàm Getter có quyền truy cập vào state thông qua tham số đầu tiên.
Cách sử dụng Store trong một Component
Trước khi có thể sử dụng store bên trong các component, chúng ta cần inject vào tất cả child component từ component gốc. Chúng ta thực hiện điều này trong main.js
, nơi mà Vue instance được tạo.
import Vue from 'vue' import App from './App.vue' import {store} from './store' import axios from 'axios' import VueAxios from 'vue-axios' Vue.use(VueAxios, axios) new Vue({ render: h => h(App), store, }).$mount('#app')
Components
Để minh họa cho cách sử dụng store trong component, mình sẽ tạo 3 components trong thư mục “components”
ProductForm.vue
ProductList.vue
InStockList.vue
ProductForm.vue
Component sẽ có một form để gửi một action đến store, mục đích là thêm một product khi người dùng nhấn nút “save”.
Vì chúng ta đã bật namespace trong module, nên tại đây, các hàm action cũng phải có tên với tiền tố (prefix) trùng với tên của module.
<template> <div class="row"> <div class="col"> <input type="text" v-model="product"> </div> <div class="col"> <input type="checkbox" v-model="inStock" id="stock"> <label for="stock">In stock</label> </div> <div class="col"> <button @click="saveProduct()">Save</button> </div> <div class="col"> <List/> <InStock/> </div> </div> </template> <script> import List from './ProductList' import InStock from './InStockList'; export default { components: {List, InStock}, data() { return { product: "", inStock: false }; }, methods: { saveProduct() { if (this.product.length) { this.$store.dispatch("products/addProduct", { name: this.product, inStock: this.inStock }); this.product = ""; this.inStock = false; } } } }; </script>
ProductList.vue
ProductList component sẽ hiển thị tất cả products, cả trong stock hay không trong stock.
Lưu ý rằng: biến product được lấy giá trị từ state object, chúng ta sẽ sử dụng mapState helper cho việc này. mapState helper sẽ tự động generate các hàm getter đã được tính toán cho chúng ta.
Vì chúng ta đã bật namespace cho module, nên cần truyền tên module vào tham số đầu tiên, tham số thứ hai là danh sách các thuộc tính mà chúng ta muốn tạo cho các hàm getter.
<template> <div v-if="products.length"> <h1>All products</h1> <ul class="list"> <li v-for="product in products" :key="product.name"> {{ product.name }} <div v-if="product.inStock">In stock</div> <div v-else>Not in stock</div> </li> </ul> </div> </template> <script> import { mapState } from 'vuex' export default { computed: { ...mapState('products', [ 'products', ]), } } </script>
InStockList.vue
Cũng giống như ProductList component, component này chỉ hiển thị các products. Chỉ khác là nó chỉ hiển thị các product nằm trong danh mục Stock.
Để làm được điều này thì component này gọi hàm inStock getter trong store, nơi mà mình đã áp dụng việc lọc sản phẩm.
<template> <div v-if="inStock.length"> <h1>Products in stock</h1> <ul class="list"> <li v-for="product in inStock" :key="product.name"> {{ product.name }} </li> </ul> </div> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters('products', [ 'inStock', ]), } } </script>
Demo sản phẩm
Về cơ bản là chúng ta đã code xong, các bạn có thể chạy thử và hưởng thụ thành quả. Mình có tạo sẵn code chạy online tại đây.
Tạm kết
Như vậy, chúng ta vừa cùng nhau tìm hiểu cách sử dụng Vuex để quản lý State của ứng dụng. Mình hi vọng bài viết sẽ giúp bạn hiểu và có thể làm việc ngon lành với Vuex.
Ngoài ra, sử dụng Vuex cũng là một trong 3 cách truyền dữ liệu giữa các component, bạn có thể tham khảo thêm bài viết này: 3 cách truyền dữ liệu trong Vue
Xin vui lòng để lại bình luận nếu có bất có thắc mắc, câu hỏi hay muốn mình viết thêm bất kỳ chủ đề liên quan đến Vue.
Công nhận dùng Vue thích thật, không lằng nhằng như React. Hay là do thói quen nhỉ?