Hôm nay sẽ là một nội rất là hay và hữu ích cho các bạn có thể ứng dụng trực tiếp vào trong dự án của các bạn, đó chính là Composable trong VueJS.
Composable là gì?
Lướt qua chút khái niệm, thì composable là việc chúng ta phân chia, chia nhỏ các chức năng phức tạp thành các chức năng nhỏ hơn và có thể tái sử dụng logic giữa các components. Và khái niệm này được nhắc đến trong Vue 3.
Thực hành Composable trong VueJS
Để cho các bạn dễ hiểu được cách áp dụng composable như thế nào, thì mình sẽ trình bày các viết trước đây cho bài toán đơn giản là xử lý form. Yêu cầu rất đơn giản là nhập name, email và một tính năng reset dữ liệu:

Để viết một tính năng như trên, thông thường thì chúng ta sẽ viết như sau để xử lý:
<template>
<div class="container">
<div class="form">
<div class="form-group">
<input v-model="form.name" placeholder="Name" class="form-control" />
</div>
<div class="form-group">
<input v-model="form.email" placeholder="Email" class="form-control" />
</div>
<button @click="resetForm" class="btn btn-primary">Reset</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'App',
setup() {
// Khởi tạo giá trị ban đầu cho form
const initialValues = {
name: '',
email: '',
};
// Khởi tạo form
const form = ref({ ...initialValues });
// Hàm reset form
const resetForm = () => {
form.value = { ...initialValues };
};
return {
form,
resetForm,
};
},
};
</script>
Như trên thì toàn bộ phần xử lý Logic đang nằm trong cùng 1 file vue. Giả sử, mình muốn thêm một form có tính năng tương tự trên chính file mà mình đang viết thì chúng ta sẽ xử lý như thế nào nhỉ? Ở đây mình không tách component để giải thích cho các bạn dễ hiểu hơn nhé:
<template>
<div class="container">
<div class="form">
<div class="form-group">
<input v-model="form.name" placeholder="Name" class="form-control" />
</div>
<div class="form-group">
<input v-model="form.email" placeholder="Email" class="form-control" />
</div>
<button @click="resetForm" class="btn btn-primary">Reset</button>
</div>
<div class="form">
<div class="form-group">
<input v-model="form2.name" placeholder="Name" class="form-control" />
</div>
<div class="form-group">
<input v-model="form2.email" placeholder="Email" class="form-control" />
</div>
<div class="form-group">
<input v-model="form2.address" placeholder="Address" class="form-control" />
</div>
<button @click="resetForm2" class="btn btn-primary">Reset</button>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'App',
setup() {
// Xử lý form 1
// Khởi tạo giá trị ban đầu cho form
const initialValues = {
name: '',
email: '',
};
// Khởi tạo form
const form = ref({ ...initialValues });
// Hàm reset form
const resetForm = () => {
form.value = { ...initialValues };
};
// Xử lý form 2
// Khởi tạo giá trị ban đầu cho form
const initialValuesForm2 = {
name: '',
email: '',
address: '',
};
// Khởi tạo form
const form2 = ref({ ...initialValuesForm2 });
// Hàm reset form
const resetForm2 = () => {
form2.value = { ...initialValuesForm2 };
};
return {
form,
resetForm,
form2,
resetForm2,
};
},
};
</script>
Như trên, chúng ta đã phải tạo thêm một vùng xử lý cho phần form 2, trong khi logic của 2 phần này hoàn toàn là giống nhau để có được kết quả như hình bên dưới:

Lúc này chính là lúc mà chúng ta thấy được composable có thể giúp ích cho chúng ta như nào. Ở đây, mình sẽ tạo ra một file là useForm.js. Tại sao là từ khóa use, thì đây là convention chung khi sử dụng composable:
import { ref } from 'vue';
export default function useForm(initialValues) {
const form = ref({ ...initialValues });
const resetForm = () => {
form.value = { ...initialValues };
};
return {
form,
resetForm,
};
}
Ở đây chỉ bao gồm việc khai báo form và tính năng reset form không khác gì cách mà chúng ta sử dụng khi viết trực tiếp trong component. Và tại file xử lý cũ ở phía trên mình sẽ viết lại như sau:
<template>
<div class="container">
<div class="form">
<div class="form-group">
<input v-model="form1.name" placeholder="Name" class="form-control" />
</div>
<div class="form-group">
<input v-model="form1.email" placeholder="Email" class="form-control" />
</div>
<button @click="resetForm1" class="btn btn-primary">Reset</button>
</div>
<div class="form">
<div class="form-group">
<input v-model="form2.name" placeholder="Name" class="form-control" />
</div>
<div class="form-group">
<input v-model="form2.email" placeholder="Email" class="form-control" />
</div>
<div class="form-group">
<input v-model="form2.address" placeholder="Address" class="form-control" />
</div>
<button @click="resetForm2" class="btn btn-primary">Reset</button>
</div>
</div>
</template>
<script>
import useForm from './composables/useForm';
export default {
name: 'App',
setup() {
// Xử lý form 1
const { form: form1, resetForm: resetForm1 } = useForm({
name: '',
email: '',
});
// Xử lý form 2
const { form: form2, resetForm: resetForm2 } = useForm({
name: '',
email: '',
address: '',
});
return {
form1,
resetForm1,
form2,
resetForm2,
};
},
};
</script>
Và kết quả chúng ta đạt được như sau:

Như các bạn đã thấy, mình đã đưa logic chung xử lý của form này về 1 file xử lý. Rõ ràng là ta đã thấy phần logic đã tách biệt hoàn toàn ra khỏi component. Với cách làm này, bạn hoàn toàn linh hoạt hơn rất nhiều khi code với VueJS, sử dụng lại được các logic dùng chung mà có thể tương tác với component. Việc này giống như việc các bạn module hóa, code của bạn sẽ dễ bảo trì hơn rất nhiều.
Vậy là mình đã chia sẻ thêm với các bạn một khái niệm mà bản thân mình thấy rất hay nếu áp dụng được vào trong dự án của các bạn. Bài viết vẫn mang tính cá nhân hóa, nên nếu có chỗ nào khó hiểu hoặc mình chưa đúng, các bạn hoàn toàn có thể đặt câu hỏi thể chúng ta cùng trao đổi.