Mở đầu

Chúng ta sẽ cùng tìm hiểu sự quan trọng của tạo một Widget Tree chính xác để đảm bảo được khả năng mở rộng và bảo trì. Nhấn mạnh cách chia nhỏ và tập trung vào các module để có thể cô lập các lỗi và ngăn chúng ảnh hưởng tới ứng dụng.

Chúng ta sẽ so sánh cách tổ chức của 2 cấu trúc widget sau: “BadApproach” class chứa tổng số lượng code lớn và “GoodApproach” class chia code thành các phần nhỏ để dễ quản lý hơn. Tôi sẽ minh họa cho mọi người thấy là việc module widget tree có thể nội địa hóa các lỗi cho từng phần cụ thể, cho phép chúng ta phát hiện chính xác phần nào bị lỗi trong ứng dụng. Và chứng minh thông qua các code ví dụ bên dưới đây là lỗi của các widget con của widget tree không làm ảnh hưởng tới các phần chính của ứng dụng. Thông qua việc chia nhỏ các class còn dễ dàng xác định lỗi ở đâu và dễ bảo trì, nâng cấp hệ thống.

Ví dụ

Cách một widget tree làm việc

widget tree
widget tree

Qua bức ảnh bên trên chúng ta thấy là có 2 cách để tạo một widget tree. Và để tiết kiệm thời gian thì hầu hết chúng ta sẽ làm theo cách đầu tiên. Tất nhiên điều này không phải một ý tưởng tốt để chúng ta scalable ứng dụng. Code ví dụ:

// A single class contain large amount of code
class BadApproach extends StatelessWidget {
  const BadApproach({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            _longWidgetFunction(),
            _longWidgetFunction(),
            Container()
          ],
        ),
      ),
    );
  }

  _longWidgetFunction(){
    // this will be contain widgets that consist
    // large amount of code
    return Container();
  }
}
dart

Và đây là cách tiếp cận thứ 2:

// Code is divided into small classes
class GoodApproach extends StatelessWidget {
  const GoodApproach({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            const WidgetA(),
            const WidgetB(),
            Container()
          ],
        ),
      ),
    );
  }
}
class WidgetA extends StatelessWidget {
  const WidgetA({super.key});

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
class WidgetB extends StatelessWidget {
  const WidgetB({super.key});

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
dart

Sau đây tôi sẽ cho các bạn thấy là tại sao cách thứ 2 lại được đánh giá cao

widget tree example
widget tree example

Giả sử Widget A code theo phong cách không tốt để tạo một widget tree:

class BadApproach extends StatelessWidget {
  const BadApproach({super.key});

  @override
  Widget build(BuildContext context) {
    const int data = 0;
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            const Text("Error Free Code"),
            const Text("Error Free Code 1"),
            const Text("Error Free Code 2"),
            const Text("Error Free Code 3"),
            Text(data as String),
          ],
        ),
      ),
    );
  }
demo.dart

Bạn có biết widget tree sẽ hoạt động như nào không ?

Như bạn đang thấy thì lỗi đang nằm ở dòng code:

    Text(data as String),
demo.dart

và đây là kết quả:

badcode
badcode

Bây giờ chúng ta sẽ thử bằng cách chia nhỏ ra nhé

class GoodApproach extends StatelessWidget {
  const GoodApproach({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Column(
          children: [
            Text("Error Free Code"),
            Text("Error Free Code 1"),
            Text("Error Free Code 2"),
            Text("Error Free Code 3"),
            Crash(),
          ],
        ),
      ),
    );
  }
}

class Crash extends StatelessWidget {
  const Crash({super.key});

  @override
  Widget build(BuildContext context) {
    dynamic data = 0;
    return Text(data as String);
  }
}
demo.dart

Bây giờ chúng ta sẽ thấy điều thú vị là giao diện render không hề bị lỗi hoàn toàn như bên trên bởi vì lỗi này thuộc về một cây con chứ không phải cái hiện tại. Việc chia nhỏ các widget như này làm cho ứng dụng vẫn hoạt động bình thường cho dù lỗi ở widget con.

goodcode
goodcode

Vì thế chúng ta có thể thấy là chúng ta render ra giao diện thành công mà không hề bị lỗi. Và ít nhất thì một số phần vẫn hoạt động vì việc widget con lỗi không ảnh hưởng đến các phần này.

crash image
crash image

Giờ chúng ta đã hiểu được phần nào về cách làm việc của widget tree chưa nào ! Giả sử lỗi nằm ở widget F thì chỉ có widget F và các cây con của F là K và L sẽ bị ảnh hưởng. Các Widget còn lại như: A, B, C, E, G, H, I và J sẽ tiếp tục hoạt động.

Tổng kết

Hãy luôn nhớ rằng chúng ta nên chia các widget thành các phần nhỏ để giảm thiêu vùng tác động trong trường hợp bị lỗi. Điều này cũng quan trọng trong việc bảo trì và nâng cấp ứng dụng Flutter của bạn.

Cảm ơn đã đọc bài. Tôi hy vọng bài viết sẽ giúp được gì đó cho mọi người. Nếu có vấn đề gì vui lòng comment xuống bên dưới ạ.