Ứng dụng Redux của bạn mở rộng như thế nào ?

Khi chúng ta bắt đầu tìm hiểu về Redux, về actions và reducers, chúng ta bắt đầu với những tutorial huyền thoại không thể đơn giản hơn như TodosApp. Nhưng trong thực tế những ứng dụng chúng ta xây dựng không hề đơn giản như vậy, chúng phức tạp hơn nhiều. Làm sao để ứng dụng của chúng ta tiếp có thể tiếp tục mở rộng nhưng vẫn dễ dàng maintain ? Làm sao chúng ta có thể đảm bảo code của chúng ta viết ra có thể maintain sau 6 tháng nữa ?

Người ta nói việc khó nhất trong lập trình là naming . Hoàn toàn đồng ý. Nhưng việc cấu trúc thư mục, tổ chức file cũng không hề dễ dàng và nó cũng ảnh hưởng đến khả năng mở rộng của ứng dụng nữa.

Trước hết chúng ta sẽ cùng tìm hiểu về cách chúng ta cấu trúc thư mục từ trước đến nay.

Function vs Feature

Có 2 cách tiếp cận được xây dựng để cấu trúc các ứng dụng: Function-firstFeature-first.

Hình bên trái phía dưới bạn có thể thấy cấu trúc thư mục function-first. Ở bên phải bạn có thể thấy cấu trúc thư mục feature-first.

Function-first

Function-first có nghĩa là các thư mục top-level được đặt tên theo mục đích của các file bên trong. Vì vậy mà chúng ta có containers, components, actions, reducers, etc.

Khả năng mở rộng của cấu trúc này thật sự kém. Khi ứng dụng phát triển thêm nhiều tính năng, chúng ta thêm rất nhiều tệp vào cùng 1 thư mục. Chúng ta sẽ kết thúc với việc scroll trong 1 thư mục với 1 đống file hỗn độn.

Vấn đề không chỉ ở đó, nó còn nằm ở sự liên kết giữa các thư mục với nhau. Một tính năng trong ứng dụng có lẽ sẽ yêu cầu file từ tất cả các folder. Và chúng ta sẽ tiếp tục kết thúc bằng việc scroll nhiều hơn.

Tuy nhiên, cách tổ chức này có 1 ưu điểm, đó là sự cô lập (isolates) React với Redux. Vì vậy, nếu ta muốn thay đổi logic quản lý state chúng ta sẽ tìm đến với Reducers, nếu chúng ta muốn thay đổi view chúng ta sẽ tìm đến Components. Thậm chí nếu muốn loại bỏ Redux hoặc React để thay bằng 1 thư viện khác thì chúng ta biết phải bỏ những thư mục nào rồi đấy (lol).

Feature-first

Feature-first có nghĩa là các thư mục top-level được đặt tên theo các tính năng của ứng dụng: product, cart, session, etc.

Khả năng mở rộng của cách tiếp cận này có vẻ tốt hơn, bởi mỗi tính năng đi kèm với 1 thư mục mới, mọi file của 1 tính năng đều nằm cùng 1 thư mục.

À ờ cái này có vẻ tốt hơn rồi đấy, vậy đây là cách mà chúng ta cần ? Có thể. Nhưng thực sự cách tiếp cận này vẫn có một chút vấn đề.

Đầu tiên là không có sự tách biệt giữa các file của Redux và React. Về lâu về dài, khi mà chúng ta có những sự thay đổi lớn về thư viện thì đây cũng là 1 vấn đề.

Thêm vào đó, trong ứng dụng của chúng ta chắc chắn sẽ có những file chẳng thuộc về 1 tính năng cố định nào cả. Chúng có thể là view như các message, notification, cũng có thể là các logic về state của Redux, hay các middleware xử lí side effect load dữ liệu dùng chung trong ứng dụng chẳng hạn. Không vấn đề gì chúng ta tạo ra folder common hoặc shared cho chúng là đc. Và khi các common tăng dần lên việc không có sự tách biệt giữa Redux và React sẽ đưa chúng ta tới trò chơi ai là ai (lol).

Why not both ?

Đọc đến đây thì hẳn là các bạn đã rõ ràng rồi, kết hợp cả 2 lại là xong. Nhưng vấn đề đặt ra là chũng ta sẽ kết hợp theo hướng nào Function-first > Feature-first hay Feature-first > Function-first

Suy nghĩ về ứng dụng của chúng ta về lâu dài, có thể chúng ta sẽ thay thế React (UI) hoặc Redux (State) bằng một thư viện nào đó khác. Do đó chúng ta có 1 ý tưởng đó là: Luôn giữ State Management file riêng biệt với UI file

Cách tiếp cận của chúng ta bắt đầu bằng việc cô lập React (UI) vào 1 thư mục gọi là views và Redux (State) gọi là state.

Sự chia tách cấp độ đầu tiên này cho chúng ta sự linh hoạt để tổ chức hai phần hoàn toàn riêng biệt của ứng dụng.

Views

Những gì còn lại của chúng ta đó là component và container, việc có chia chúng theo Function-first hay không phụ thuộc vào bạn, còn cá nhân mình thì Feature-first là đủ rồi

State

Mỗi tính năng của ứng dụng nên ứng với actions và reducers riêng biệt, vì vậy chúng ta vẫn sẽ tiếp cận bằng phương pháp Feature-first.

Ducks modular nguyên bản là một đơn giản hóa tốt cho redux và cung cấp một cách cấu trúc để có thêm mới mới các tính năng trong ứng dụng của chúng ta.

Tuy nhiên, khi ứng dụng mở rộng hơn nữa, chúng ta nhận ra rằng một file duy nhất cho một tính năng sẽ trở nên quá lộn xộn và khó để duy trì về lâu dài.

Và đây là cách mà re-ducks được tạo ra. Giải pháp đó là tách duck từ file thành folder

duck/
├── actions.js
├── index.js // export data theo nguyên tắc của Duck modular
├── operations.js // chained operation khi sử dụng middleware như thunk, saga... 
├── reducers.js
├── selectors.js 
├── tests.js
├── types.js
├── utils.js

Một duck folder phải:

  • chứa toàn bộ logic để xử lý chỉ 1 khái niệm trong ứng dụng.
  • có file index.js để exports theo nguyên tắc của Duck modular nguyên bản.
  • giữ code với mục đích tương tự trong cùng file. Ví dụ như reducers, selectors, actions.
  • chứa test liên quan đến duck.

Sau khi cấu trúc lại thư mục state của chúng ta sẽ tương tự như thế này:

Kết

Việc chúng ta cấu trúc thư mục ứng dụng, như trên chung quy lại vẫn là cố gắng tách State management ra khỏi UI. Việc này tương tự với việc chúng ta tách state khỏi presentational components. Các thành phần trong ứng dụng càng cô lập chúng ta càng dễ maintain.

Cuối cùng, không có một tiêu chuẩn nào là tuyệt đối, hãy chọn cấu trúc nào bạn cảm thấy thoải mái và tốt nhất cho bạn.

Source

https://medium.freecodecamp.org/scaling-your-redux-app-with-ducks-6115955638be