Bước 3 - Sử dụng store để lưu và phân phối dữ liệu
Nếu bạn đã từng làm việc với các ứng dụng single-page, bạn hẳn đã từng nghe qua các thư viện quản lý state như Redux hoặc Vuex. Cấu trúc centralized state management này rất hiệu quả trong việc giữ data của bạn đồng nhất ở tất cả các component, giúp việc tương tác giữa những component khác nhau trở nên dễ dàng và tránh bị props drilling.
Điều tuyệt vời hơn nữa là khi bạn khởi tạo một dự án Zalo Mini App, một store đã được cài đặt sẵn trong file src/store.js
. Bạn chỉ việc khai báo state và dùng các hàm được cung cấp như useStore
, dispatch
để có thể tương tác với store trong các components một cách dễ dàng.
Tìm hiểu thêm về store của Zalo Mini App: ZMP Store
1. Đọc dữ liệu từ store
Khai báo state
Một store sẽ bao gồm 3 phần chính: state, getters và actions. State đơn giản chỉ là một object javascript, nơi bạn có thể quăng bất cứ dữ liệu nào cần dùng chung ở nhiều components vào đó. Ví dụ:
import { createStore } from 'zmp-core/lite';
const store = createStore({
state: {
user: null,
showCheckout: false,
shipping: false,
categories: ['Cà Phê', 'Trà', 'Bánh Ngọt', 'Thức Uống Khác'],
loadingProducts: true,
products: [],
loadingOrders: true,
orders: [],
selectedAddress: null,
shops: [
{
selected: true,
name: 'VNG Campus D7',
address:
'Lô Z.06 - Đường số 13, KCX Tân Thuận, P. Tân Thuận Đông, Q.7, TP Hồ Chí Minh.',
open: { hour: 8, minute: 0 },
close: { hour: 17, minute: 0 }
},
{
name: '210 Nguyễn Trãi',
address:
'210 Nguyễn Trãi, Phường Phạm Ngũ Lão, Quận 1, Thành phố Hồ Chí Minh',
open: { hour: 8, minute: 0 },
close: { hour: 17, minute: 0 }
},
{
name: 'Opera House',
address:
'07 Công Trường Lam Sơn, Bến Nghé, Quận 1, Thành phố Hồ Chí Minh',
open: { hour: 8, minute: 0 },
close: { hour: 17, minute: 0 }
},
{
name: 'Sài Gòn Tower',
address: '29 Lê Duẩn, Bến Nghé, Quận 1, Thành phố Hồ Chí Minh',
open: { hour: 8, minute: 0 },
close: { hour: 17, minute: 0 }
},
{
name: 'Sala 2',
address:
'125 Nguyễn Cơ Thạch, An Lợi Đông, Quận 2, Thành phố Hồ Chí Minh',
open: { hour: 8, minute: 0 },
close: { hour: 17, minute: 0 }
}
],
cart: [],
discounts: [
{
code: 'GIAM20K',
name: 'Ưu đãi 20K! Đặt Coffee Shop trên Zalo.',
expireDate: '10/05/2021',
image: 'discount-1'
},
{
code: 'GIAM35%',
name: 'Giảm 35% cho đơn hàng từ 5 món, tối đa 59K...',
expireDate: '10/05/2021',
image: 'discount-2'
},
{
code: 'GIAM30K',
name: 'Ưu đãi 30K cho đơn hàng 149K - Áp dụng cho dịch ...',
expireDate: '10/05/2021',
image: 'discount-3'
}
],
selectedDiscount: null,
addresses: [],
shippingTime: [new Date(), new Date().getHours(), new Date().getMinutes()],
note: ''
},
getters: {},
actions: {}
});
export default store;
Khai báo getters
Dữ liệu được khai báo trong state sẽ không thể được truy cập trực tiếp từ phía component. Chúng ta cần các getters cho việc này. Getter thực ra chỉ là một hàm javascript, nhận vào một object là { state }
và trả ra một giá trị mong muốn từ state đó. Ví dụ getter để lấy user ra khỏi state sẽ có dạng như thế này:
const store = createStore({
state: {
...
},
getters: {
user({ state }) {
return state.user
},
},
actions: {
}
})
Nhiều bạn sẽ thắc mắc tại sao không truy cập trực tiếp user
từ state
, mà phải khai báo getters? Câu trả lời là vì getters giúp bạn giữ data trong store được đơn giản nhất có thể, và đưa những phép tính phức tạp ra khỏi component. Ví dụ bạn chỉ cần một mảng cart
để lưu lại các item trong giỏ hàng, và dùng 2 getters để tính tổng tiền và tổng số lượng:
const store = createStore({
state: {
cart: [
{
id: 1,
subtotal: 37000,
quantity: 2
},
{
id: 2,
subtotal: 14000,
quantity: 1
}
]
},
getters: {
totalQuantity({ state }) {
return state.cart.reduce((total, item) => total + item.quantity, 0);
},
totalAmount({ state }) {
return state.cart.reduce((total, item) => total + item.subtotal, 0);
}
}
});
Ngoài ra getters còn giúp bạn lưu lại giá trị của những phép tính, giúp tăng performance cho Zalo Mini App của bạn khi bạn có nhiều hàm xử lý dữ liệu có độ phức tạp cao.
Hãy cố gắng đẩy những logic tính toán vào getters nhiều nhất có thể. Đừng chỉ khai báo toàn những getter chỉ trả về state như thế này:
const store = createStore({
state: {
...
},
getters: {
user({ state }) {
return state.user
},
categories({ state }) {
return state.categories
},
products({ state }) {
return state.products
},
loadingProducts({ state }) {
return state.loadingProducts
},
shops({ state }) {
return state.shops
},
}
})
useStore
Có state và getters rồi, giờ làm thế nào để đưa dữ liệu vào component? Rất dễ dàng, sử dụng useStore
. useStore
nhận vào một string, là tên của getter và trả ra giá trị đã được tính toán sẵn của getter đó. Ngoài ra khi sử dụng useStore, component của bạn sẽ được tự động render lại khi giá trị của getter này thay đổi.
import { useStore } from 'zmp-framework/react';
const Heading = () => {
const selectedShop = useStore('selectedShop');
return (
<List className='m-0'>
<ListItem>
<Avatar src={pickup} />
<div className='ml-4 flex-1'>
<Text bold className='mb-0'>
Coffee Shop
</Text>
<Text className='ellipsis mb-0'>
{selectedShop.name} - {selectedShop.address}
</Text>
</div>
<FollowOrMessage />
</ListItem>
</List>
);
};
useStore bản chất là một custom hook, và một điều bạn luôn phải nhớ khi sử dụng React hooks là không được bỏ nó vào các câu lệnh điều kiện hoặc dòng lặp. Ví dụ:
const Heading = () => {
if (someCondition)
const selectedShop = useStore('selectedShop')
}
return <Shop data={selectedShop} />
}
hay
const Heading = () => {
if (loading) return <Loading />;
const selectedShop = useStore('selectedShop');
return <Shop data={selectedShop} />;
};
2. Chỉnh sửa dữ liệu trong store
Khai báo actions
Cũng giống như những store khác, một trong những quy tắc khi thay đổi dữ liệu trong store là chúng ta không trực tiếp gán giá trị cho state mà sẽ thực hiện thông qua actions
. Actions đơn giản là một hàm nằm trong cụm actions
của store, nhận vào state
và dispatch
trong tham số thứ nhất, tham số thứ hai là payload khi dispatch và thân hàm chứa những chỉnh sửa cần thiết tới state
:
const store = createStore({
state: {
...
},
getters: {
...
},
actions: {
addToCart({ state }, item) {
state.cart = state.cart.concat(item)
},
}
})
Khi khai báo nested object bên trong state, khi action muốn thay đổi giá trị bên trong object, phải gán lại giá trị mới cho object chứ không trực tiếp mutate object. Ví dụ:
const store = createStore({
state: {
...
},
getters: {
...
},
actions: {
addToCart({ state }, item) {
state.cart.push(item)
// Các getters sẽ không nhận biết được cart đã được thay đổi
},
}
})
Dispatch action từ phía component
Khi đã khai báo action, chúng ta có thể dispatch action đó từ phía component để thực hiện thay đổi state:
import store from '../store';
<Button onClick={() => store.dispatch('addToCart', item)}>Add to cart</Button>;