Best Practice: Cấu trúc components và file trong dự án Vue

Mở đầu

Thành phần nhỏ nhất trong kiến trúc của VueJS là các component. Khi bắt đầu viết một phần mềm, bạn tiến hành xây dựng các component, chia nhỏ các thành phần của trang web thành các component khác nhau để có thể sử dụng lại chúng...
Tất cả đều hoạt động rất hoàn hảo cho đến khi phần mềm trở nên phức tạp, có hàng chục trang, hàng trăm chức năng khác nhau, tương ứng với nó là số lượng component tăng lên. Lúc này mọi thứ bắt đầu trở nên lộn xộn. Thật khó để tìm đúng component và biết chức năng của nó trong đống hỗn độn đó, gây ảnh hưởng rất không tốt cho quá trình phát triển phần mềm và bảo trì sau này.
Trong bài viết này, chúng ta sẽ xem xét một best practice để có thể phân tách, sắp xếp, cấu trúc các component trong phần mềm của mình.

First Steps: Components

Cấu trúc cơ bản thường thấy khi bắt đầu xây dựng phần mềm với Vue là có một folder components, cùng một file main.js và một file App.vue

./src
├── App.vue
├── main.js
├── components/

Sau chỉ một vài feature đầu tiên, ta sẽ có thêm một số component:

./components
├── HomePage.vue
├── AppHeader.vue
├── AppFooter.vue
├── SideBar.vue
├── UserPage.vue
├── PageTitile.vue
├── Container.vue
├── Form.vue
├── Table.vue

Khi nhìn vào cấu trúc các file trong thư mục /components bạn có thể dễ dàng nhận biết một dạng component chung đại diện cho các trang chính: /pages. Do đó, chúng ta sẽ có một thư mục /pages để chứa những components này.
Lúc này cấu trúc thư mục của chúng ta sẽ như sau:

./src
├── App.vue
├── main.js
├── components
│   ├── AppHeader.vue
│   ├── AppFooter.vue
│   ├── SideBar.vue
│   ├── PageTitile.vue
│   ├── Container.vue
│   ├── Form.vue
│   ├── Table.vue
└── pages
    ├── Home.vue
    ├── User.vue

Phân chia thành /pages giúp chúng ta dễ dàng tìm kiếm component theo các trang cụ thể mà không cần mò tìm trong một đống các components khác.
Trong website của mình, chúng ta cũng thấy có những thành phần xuất hiện ở hầu hết tất cả các trang như Header, Footer, SideBar,.... Những các component này khi đứng riêng lẻ lại không phải là 1 trang hoàn chỉnh, nhưng nó vẫn nằm trong bố cục của website. Một trang chỉ tồn tại duy nhất 1 component này, chúng không bị lặp lại. Lúc này, chúng ta sẽ nghĩ tới phân tách chúng vào một folder riêng nằm trong folder /components: /layout.
Cấu trúc thư mục sẽ như sau:

./src
├── App.vue
├── main.js
├── components
│   ├── layout
│   │   ├── Header.vue
│   │   ├── Fotter.vue
│   │   ├── Breadcrumb.vue
│   │   └── Sidebar.vue
│   ├── Container.vue
│   ├── Form.vue
│   ├── Table.vue
└── pages
    ├── Home.vue
    ├── User.vue

Còn về các component Container.vue Form.vue, Table.vue,... ta có thể thấy chúng có thể sử dụng lại trong nhiều trang khác nhau, vì vậy không nên đặt chúng trong folder /pages và chúng cũng không phải là duy nhất nên cũng không nằm trong folder /layout. Trong trường hợp này, chúng ta tạo thêm folder mới là: /article.

./src
├── App.vue
├── main.js
├── components
│   ├── layout
│   │   ├── Header.vue
│   │   ├── Fotter.vue
│   │   ├── Breadcrumb.vue
│   │   └── Sidebar.vue
│   ├── article
│   │   ├── Container.vue
│   │   ├── Form.vue
│   │   └── Table.vue
└── pages
    ├── Home.vue
    ├── User.vue

Cấu trúc thư mục lúc này đã khá gọn gàng đúng không nào. Trong một số website, chúng ta có thể phải chia nhỏ các component tới mức từng button, hay từng ô input. Vậy lúc này chúng ta sẽ để chúng ở đâu? Các component này quá nhỏ để có thể xếp vào /article và chúng cũng không thực hiện một logic nghiệp vụ nào, chúng chỉ cần emit một số sự kiện cụ thể.
Với trường họp này, chúng ta sẽ tạo thêm folder /ui hoặc /atoms để chứa các component nhỏ đó.

./src
├── App.vue
├── main.js
├── components
│   ├── layout
│   │   ├── Header.vue
│   │   ├── Fotter.vue
│   │   ├── Breadcrumb.vue
│   │   └── Sidebar.vue
│   ├── article
│   │   ├── Container.vue
│   │   ├── Form.vue
│   │   └── Table.vue
│   ├── ui
│   │   ├── Button.vue
│   │   ├── Input.vue
│   │   ├── Radio.vue
│   │   └── Select.vue
└── pages
    ├── Home.vue
    ├── User.vue

Một thực tế khác mà khi làm việc sẽ gặp phải đó là trong một trang sẽ có nhiều thành phần khác nhau. Ví dụ: trong trang Home có thể có phần About, Movie, Product, Review,... Những component này chỉ tồn tại trong trang Home nên chúng ta sẽ đặt chúng trong folder /pages/HomePage. Ta sẽ đổi tên Home.vue thành index.vue để module hóa và không cần thay đổi cách import component này.

./src
├── App.vue
├── main.js
├── components
│   ├── layout
│   │   ├── Header.vue
│   │   ├── Fotter.vue
│   │   ├── Breadcrumb.vue
│   │   └── Sidebar.vue
│   ├── article
│   │   ├── Container.vue
│   │   ├── Form.vue
│   │   └── Table.vue
│   ├── ui
│   │   ├── Button.vue
│   │   ├── Input.vue
│   │   ├── Radio.vue
│   │   └── Select.vue
└── pages
    ├── HomePage
    │   ├── index.vue
    │   ├── About.vue
    │   ├── Movie.vue
    │   └── Review.vue
    ├── User.vue

Cấu trúc thư mục components của chúng ta lúc này đã hoàn chỉnh và bao quát hầu hết các trường hợp trong thực tế.

Other: Vuex, vue-router and css

Ngoài các component, website của chúng ta còn sử dụng thêm nhiều plugin, và các css custom khác nhau. Chúng ta sẽ xem xét thêm các trường hợp đó.
Với VueX và Vue-Router, chúng là những thư viện mà hầu hết dự án nào cũng phải sử dụng. Chính vì thế, chúng ta sẽ dành cho chúng 1 folder riêng. Và cũng được chia thành những modules tương ứng với pages

./src
├── App.vue
├── main.js
├── components
│   ├── layout
│   │   ├── Header.vue
│   │   ├── Fotter.vue
│   │   ├── Breadcrumb.vue
│   │   └── Sidebar.vue
│   ├── article
│   │   ├── Container.vue
│   │   ├── Form.vue
│   │   └── Table.vue
│   ├── ui
│   │   ├── Button.vue
│   │   ├── Input.vue
│   │   ├── Radio.vue
│   │   └── Select.vue
├── pages
│   ├── HomePage
│   │   ├── index.vue
│   │   ├── About.vue
│   │   ├── Movie.vue
│   │   └── Review.vue
│   ├── User.vue
├── store
│   ├── modules
│   │   ├── auth
│   │   ├── home
│   │   ├── product
│   │   └── account
│   └── index.js 
├── router
│   ├── modules
│   │   ├── auth
│   │   ├── home
│   │   ├── product
│   │   └── account
│   └── index.js 

Về css, thông thường chúng ta sẽ viết luôn vào thẻ <style scoped></style>. Nhưng cũng có những thuộc tính được sử dụng ở nhiều componet, được áp dụng cho toàn bộ website như reset, color, và cả file main.css... Lúc này, chúng ta sẽ tạo thêm 1 folder để chứa chúng và @import '@/styles/color'; ở component cần sử dụng những thuộc tính đó.

./src
├── App.vue
├── main.js
├── components
│   ├── layout
│   │   ├── Header.vue
│   │   ├── Fotter.vue
│   │   ├── Breadcrumb.vue
│   │   └── Sidebar.vue
│   ├── article
│   │   ├── Container.vue
│   │   ├── Form.vue
│   │   └── Table.vue
│   ├── ui
│   │   ├── Button.vue
│   │   ├── Input.vue
│   │   ├── Radio.vue
│   │   └── Select.vue
├── pages
│   ├── HomePage
│   │   ├── index.vue
│   │   ├── About.vue
│   │   ├── Movie.vue
│   │   └── Review.vue
│   ├── User.vue
├── store
│   ├── modules
│   │   ├── auth
│   │   ├── home
│   │   ├── product
│   │   └── account
│   └── index.js 
├── router
│   ├── modules
│   │   ├── auth
│   │   ├── home
│   │   ├── product
│   │   └── account
│   └── index.js
├── styles
    ├── _color.css
    ├── _reset.css
    ├── _layout.css
    └── main.css

Kết bài

Đến đây, chúng ta đã được tiếp cận với một cách phân chia component và các file trong một ứng dụng VueJS khá là dễ hiểu, rõ ràng. Hi vọng sẽ giúp cho dự án của chúng ta dễ dàng phát triển, dễ dàng mở rộng và bớt "cọc" hơn khi phải quay lại maintain nó.
Bài viết tham khảo từ dự án SIPS và: https://vueschool.io/articles/vuejs-tutorials/structuring-vue-components/