Sử dụng flat state trong Vue Store
Bài viết được sự cho phép của tác giả Lưu Bình An
Đầu tiên chúng ta cần trả lời câu hỏi global state có phải là phương thuốc chữa bá bệnh cho các vấn đề liên quan tới state
? Mình chỉ đưa dữ liệu vào Vuex store như là lựa chọn cuối cùng và có một lý do cụ thế để phải sử dụng. Điều thứ 2, luôn giữ global state ở dạng cây một cấp, nghĩa là chúng ta không lồng dữ liệu liệu vào nhau như bên dưới
Đọc thêm https://markus.oberlehner.net/blog/should-i-store-this-data-in-vuex/ để có khái niệm khi nào cần dữ liệu trong store và khi nào không.
Quan điểm về flat state (không lưu dữ liệu lồng nhau trong store) được lấy cảm hứng từ chú Matt Biilmann chia sẽ về quan điểm về Redux sau khi làm cái dashboard cho Netlify trong bài phỏng vấn Architecting the Netlify Dashboard with React and Redux
cap_1: {
cap_2: {
cap_3: {
}
}
}
Rất khó để sync dữ liệu ở dạng lồng ghép như vậy.
Ví dụ, có danh sách bài viết, mỗi bài viết được nhét thông tin tác giả bên trong, có nhiều bài viết có cùng một tác giả, rồi ngày đẹp trời tác giả này đổi tên, thì chúng ta phải đi sync lại toàn bộ tất cả bài viết của ổng.
const articles = [
// bài viết này được load trước
{
author: {
avatar: 'https://picsum.photos/id/1011/25',
id: 1,
name: 'Jane Doe',
},
id: 1,
intro: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr.',
title: 'Lorem Ipsum',
},
// tác giả đó ổng vô đổi avatar,
// rồi chúng ta load thêm bài viết
// avatar của ổng đã không còn như xưa
{
author: {
avatar: 'https://picsum.photos/id/2000/25',
id: 1,
name: 'Jane Doe',
},
id: 2,
intro: 'Stet clita kasd gubergren, no sea takimata sanctus est.',
title: 'Dolor sit',
},
];
Cách mà chúng ta nên lưu, tách riêng 2 thằng
const articles = {
1: {
author: 1,
id: 1,
intro: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr.',
title: 'Lorem Ipsum',
},
2: {
author: 1,
id: 2,
intro: 'Stet clita kasd gubergren, no sea takimata sanctus est.',
title: 'Dolor sit',
},
};
const authors = {
1: {
avatar: 'https://picsum.photos/id/2000/25',
id: 1,
name: 'Jane Doe',
},
};
Những kiểu thực thể khác nhau, chúng ta tách ra thành các module riêng biệt, dùng khái niệm foreign key (khóa ngoại) như trong database
// src/store/modules/article.js
import Vue from 'vue';
import { normalizeRelations, resolveRelations } from '../helpers';
import articleService from '../../services/article';
const state = {
byId: {},
allIds: [],
};
const getters = {
// trả về một article với giá trị id được truyền vào
find: (state, _, __, rootGetters) => id => {
// dùng ID để lấy thông tin tác giả
return resolveRelations(state.byId[id], ['author'], rootGetters);
},
// trả về danh sách articles
list: (state, getters) => {
return state.allIds.map(id => getters.find(id));
},
};
const actions = {
load: async ({ commit }) => {
const articles = await articleService.list();
articles.forEach((item) => {
commit('add', normalizeRelations(item, ['author']));
// thêm hoặc update order
commit('author/add', item.author, {
root: true,
});
});
},
};
const mutations = {
add: (state, item) => {
Vue.set(state.byId, item.id, item);
if (state.allIds.includes(item.id)) return;
state.allIds.push(item.id);
},
};
export default {
actions,
getters,
mutations,
namespaced: true,
state,
};
// src/store/helpers.js
export function normalizeRelations(data, fields) {
return {
...data,
...fields.reduce((prev, field) => ({
...prev,
[field]: Array.isArray(data[field])
? data[field].map(x => x.id)
: data[field].id,
}), {}),
};
}
export function resolveRelations(data, fields, rootGetters) {
return {
...data,
...fields.reduce((prev, field) => ({
...prev,
[field]: Array.isArray(data[field])
? data[field].map(x => rootGetters[`${field}/find`](x))
: rootGetters[`${field}/find`](data[field]),
}), {}),
};
}
Sử dụng
<template>
<div id="app">
<ArticleList :articles="articles"/>
</div>
</template>
<script>
// src/App.vue
import { mapActions, mapGetters } from 'vuex';
import ArticleList from './components/ArticleList';
export default {
name: 'App',
components: {
ArticleList,
},
computed: {
...mapGetters('article', { articles: 'list' }),
},
created() {
this.loadArticles();
},
methods: {
...mapActions('article', { loadArticles: 'load' }),
},
};
</script>
Trong component App.vue
chúng ta lấy các getter và action trong article
module, khi vừa khởi tạo component, gọi action loadArticle
để lấy dữ liệu
<template>
<ul class="ArticleList">
<li
v-for="article in articles"
:key="article.id"
>
<h2>{{ article.title }}</h2>
<p>{{ article.intro }}</p>
<div class="ArticleList__author">
<img class="ArticleList__avatar" :src="article.author.avatar" :alt="article.author.name">
{{ article.author.name }}
</div>
</li>
</ul>
</template>
<script>
export default {
name: 'ArticleList',
props: {
articles: {
required: true,
type: Array,
},
},
};
</script>
Nhờ vào các hàm getter và resolveRelations()
, chúng ta có thể dễ dàng truy cập author của từng article
📜 Make your Vuex State Flat: State Normalization with Vuex
Bài viết gốc được đăng tải tại vuilaptrinh.com
Có thể bạn quan tâm:
- Vuex là gì?
- Viết Unit Test cho Vue component cho người mới bắt đầu
- Giới thiệu Vuex cho người mới bắt đầu
Xem thêm các việc làm Developer hấp dẫn tại TopDev
- B BenQ RD Series – Dòng Màn Hình Lập Trình 4k+ Đầu Tiên Trên Thế Giới
- i iOS 18 có gì mới? Có nên cập nhật iOS 18 cho iPhone của bạn?
- G Gamma AI là gì? Cách tạo slide chuyên nghiệp chỉ trong vài phút
- P Power BI là gì? Vì sao doanh nghiệp nên sử dụng PBI?
- K KICC HCMC x TOPDEV – Bước đệm nâng tầm sự nghiệp cho nhân tài IT Việt Nam
- T Trello là gì? Cách sử dụng Trello để quản lý công việc
- T TOP 10 SỰ KIỆN CÔNG NGHỆ THƯỜNG NIÊN KHÔNG NÊN BỎ LỠ
- T Tìm hiểu Laptop AI – So sánh Laptop AI với Laptop thường
- M MySQL vs MS SQL Server: Phân biệt hai RDBMS phổ biến nhất
- S SearchGPT là gì? Công cụ tìm kiếm mới có thể đánh bại Google?