Separation of Concerns in React – Đơn giản, nhưng khó khăn

Giới thiệu

React, một thư viện JavaScript phổ biến để xây dựng giao diện người dùng, cung cấp cho các nhà phát triển tính linh hoạt và sức mạnh để tạo ra các ứng dụng phức tạp. Tuy nhiên, khi ứng dụng React của bạn ngày càng phát triển về kích thước và phức tạp, bạn có thể gặp tình huống cần tái sử dụng một thành phần, nhưng việc này lại phức tạp hơn nhiều so với mong đợi. Điều này thường xảy ra khi một thành phần nhiễm nhiệm bởi logic phức tạp không chỉ ảnh hưởng đến hành vi của nó mà còn tác động vào hành vi của các thành phần khác, khiến việc tái sử dụng thành phần và giao diện người dùng của nó trở nên khó khăn.

Vậy làm cách nào để giải quyết vấn đề này?

Giải pháp cho vấn đề này nằm ở việc hiểu và thực hiện khái niệm “Separation of Concerns” (Tách rời các yếu tố). Trong bài viết này, chúng ta sẽ khám phá ý nghĩa của Separation of Concerns trong phát triển phần mềm và cách nó có thể được áp dụng trong React để làm cho mã của bạn dễ bảo trì hơn, dễ tái sử dụng hơn và dễ mở rộng hơn.

Định nghĩa separation of concerns?

Trong lĩnh vực phát triển phần mềm, tách biệt quan tâm là một nguyên tắc cơ bản. Nó liên quan đến việc đảm bảo rằng mỗi thành phần của chương trình của bạn phục vụ một mục đích duy nhất và được định nghĩa rõ ràng. Trong React, sự tách biệt này bao gồm việc chia mã của bạn thành hai loại chính: phần logic hoặc quản lý dữ liệu và phần hiển thị giao diện người dùng.

Bằng cách tách biệt logic khỏi giao diện, bạn có thể tạo ra sự phân biệt rõ ràng giữa cách ứng dụng của bạn hoạt động và cách nó xuất hiện trước người dùng. Sự phân chia rõ ràng này có nhiều lợi ích:

  • Bảo trì: Việc tìm kiếm, chỉnh sửa và gỡ lỗi mã trở nên dễ dàng hơn khi logic và hiển thị được tách biệt.
  • Tái sử dụng: Bạn có thể tái sử dụng các thành phần hiển thị với các nguồn dữ liệu khác nhau, và bạn có thể tái sử dụng các thành phần logic trong các phần khác của ứng dụng của bạn.
  • Mở rộng: Khi ứng dụng của bạn phát triển, bạn có thể quản lý nó hiệu quả hơn bằng cách giữ các thành phần logic và hiển thị tách biệt.

Container components và Presentation components

Ví dụ

Trong ví dụ được cung cấp, có một component có tên là UIController bao gồm một nhãn và một nút đặt lại. Lợi ích của thành phần này là nó có thể được sử dụng ở nhiều vị trí khác nhau để sửa đổi các thuộc tính khác như FontStyle và TextTransform. Thành phần cũng chứa một hàm có tên là handleResetValue. Bây giờ, tôi muốn sử dụng thành phần này ở một nơi khác, nhưng với một phương thức đặt lại khác và nó không quan tâm đến styleKey. Làm thế nào để làm được điều đó? Rất đơn giản, bạn chỉ cần viết một thành phần UIController mới hoặc thêm một prop tùy chỉnh vào thành phần hiện có ?.

Được rồi, điều đó có vẻ tốt và giải quyết được vấn đề của bạn, nhưng nó tạo ra một component trùng lặp. Để làm cho điều này trở nên khả thi, đây là mấu chốt: đừng đặt bất kỳ mã logic nào bên trong thành phần UIController. Thay vào đó, tạo một thành phần chứa riêng biệt để xử lý logic và truyền dữ liệu và các hàm cần thiết dưới dạng props cho UIController.

Để sử dụng component này chúng ta chỉ cần đưa nó vào component cha là được

Hiểu về Container và Presentation Components

Trong React, các Container components (thành phần chứa) và các Presentation components (thành phần trình bày) đóng vai trò riêng biệt:

  • Các Thành Phần Chứa: Các thành phần này chịu trách nhiệm truy xuất dữ liệu, quản lý trạng thái và logic. Chúng hoạt động như trung gian giữa dữ liệu và các thành phần trình bày. Các thành phần chứa truy xuất dữ liệu từ các API, quản lý trạng thái của ứng dụng của bạn và truyền dữ liệu và các hàm đến các thành phần trình bày.
  • Các Thành Phần Trình Bày: Các thành phần này tập trung vào việc hiển thị các phần tử giao diện người dùng và hiển thị dữ liệu từ các thành phần chứa. Chúng không nhận biết được nơi mà dữ liệu đến từ đâu hoặc cách nó được xử lý; mục đích duy nhất của chúng là hiển thị dữ liệu và tương tác với người dùng.

Sự tách rời này là một khái niệm đơn giản nhưng mạnh mẽ đảm bảo một mã nguồn dễ đọc và bảo trì. Bằng việc tuân theo mẫu này, bạn có thể tránh các lỗi phổ biến mà nhiều nhà phát triển gặp phải khi bắt đầu sử dụng React, như việc kết hợp logic và trình bày trong một thành phần duy nhất. Những lỗi như vậy có thể dẫn đến mã nguồn khó điều khiển và dễ gây lỗi.

Dễ phải không?

Ví dụ ở trên có thể dường như cơ bản, nhưng nhiều nhà phát triển vẫn mắc phải những lỗi, tôi cũng từng mắc lỗi như vậy trước đây. Những lỗi này có thể gây ra sự rối ren. Tôi đã gặp nhiều trường hợp mã nguồn giống như vậy, và điều tôi phải làm là tạo một thành phần khác để đảm bảo tính tái sử dụng cho thành phần hiện có.

Ví dụ khác

Chúng ta sẽ cùng nhau tạo một component Countdown Timer, component này sẽ bao gồm các thành phần timer, nút start và nút stop

Trước hết, hãy tạo ra những component cho giao diện

TimerDisplay component:

StartButton component:

StopButton component:

Giờ chúng ta có thể tạo một component để chứa tất cả những component bên trên

Như bạn có thể thấy, component ở trên chứa tất cả những component về UI. Mình có tạo ra một React hook useCountdownTimer để xử lý phần logic. Hãy viết nó ra nào:

Giờ chúng đã có một component Countdown Timer hoàn chỉnh, nhưng nó đang gặp một vấn đề về hiệu năng. Bạn có thể fix nó bằng cách sử dụng useCallback và memo của React. Mình sẽ không đề cập tới 2 phần đó ở bài viết này. Bạn có thể tìm hiểu thêm ở phần tài liệu của React

Những thách thức và lỗi thường gặp

Mặc dù khái niệm về sự tách rời của các yếu tố là khá dễ hiểu, nhưng các nhà phát triển thường gặp khó khăn khi thực hiện nó trong dự án React của họ. Dưới đây là một số sai lầm phổ biến và cách tránh chúng:

  • Phức tạp hóa quá trình Tách rời: Tìm sự cân bằng đúng đắn giữa các thành phần chứa và các thành phần trình bày có thể thách thức. Hãy tránh tạo quá nhiều thành phần chứa bao quá nhiều thành phần trình bày, vì điều này có thể dẫn đến sự phức tạp không cần thiết.
  • Quản lý state: Xử lý state trong một ứng dụng React có thể khá khó khăn, đặc biệt khi làm việc với các component lồng vào nhau nhiều cấp. Một thư viện quản lý state có thể giúp quản lý trạng thái một cách có trật tự hơn (như redux, recoil,…)
  • Giao tiếp giữa các component: Truyền dữ liệu và các hàm từ các thành phần chứa tới các thành phần trình bày có thể gây phiền toái. Sử dụng props, context, hoặc các thư viện bên thứ ba quản lý trạng thái để làm cho luồng dữ liệu trơn tru hơn.

Tổng kết

Trong quá trình phát triển React app, suy nghĩ về tính tái sử dụng là một bước tích cực, nhưng cũng quan trọng không kém là xem xét tính bảo trì và khả năng mở rộng. Sự tách rời của các yếu tố thông qua các thành phần chứa và trình bày là một kỹ thuật quý báu giúp đơn giản hóa quá trình phát triển, khuyến khích tính tái sử dụng mã nguồn và nâng cao tính bảo trì của mã nguồn.

Bằng cách áp dụng nguyên tắc này, bạn có thể quản lý một cách hiệu quả các ứng dụng React phức tạp, làm cho mã nguồn của bạn trở nên mô đun hóa hơn và xây dựng các ứng dụng có khả năng mở rộng, dễ dàng bảo trì và mở rộng.

Khi bạn tiếp tục hành trình trong phát triển React app, hãy nhớ ưu tiên việc tách rời các yếu tố. Tương lai của bạn (và các nhà phát triển khác) sẽ cảm ơn bạn vì điều đó.

ThanhLM

Comments

Let’s make a great impact together

Be a part of BraveBits to unlock your full potential and be proud of the impact you make.