StatelessWidget vs StatefulWidget

1. Khái niệm

Trong Flutter, StatelessWidget và StatefulWidget là hai loại Widget cơ bản dùng để xây dựng giao diện.

Điểm khác biệt quan trọng nhất nằm ở State (trạng thái).

StatelessWidget: Widget không có State nội bộ có thể thay đổi.

StatefulWidget: Widget có State nội bộ có thể thay đổi trong quá trình ứng dụng chạy.

Có thể hiểu đơn giản:

StatelessWidget
→ UI phụ thuộc vào dữ liệu hiện tại
→ Không tự quản lý State thay đổi

StatefulWidget
→ UI có dữ liệu thay đổi
→ Có State để quản lý dữ liệu đó

2. StatelessWidget

StatelessWidget là Widget dùng để xây dựng những thành phần UI không cần quản lý trạng thái thay đổi bên trong Widget.

Ví dụ:

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

  @override
  Widget build(BuildContext context) {
    return const Text(
      'Welcome to Flutter',
    );
  }
}

Widget trên chỉ hiển thị một dòng chữ.

Không có dữ liệu nào bên trong Widget cần thay đổi trong quá trình sử dụng.

Đặc điểm

StatelessWidget
├── Không có State riêng
├── Không sử dụng setState()
├── Có phương thức build()
├── UI phụ thuộc vào dữ liệu được cung cấp
└── Phù hợp với UI tĩnh hoặc UI không quản lý trạng thái nội bộ

3. Ví dụ StatelessWidget

Một ProductCard có thể được xây dựng bằng StatelessWidget:

class ProductCard extends StatelessWidget {
  final String name;
  final double price;

  const ProductCard({
    super.key,
    required this.name,
    required this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          Text(name),
          Text('$price VNĐ'),
        ],
      ),
    );
  }
}

Sử dụng:

ProductCard(
  name: 'Nike Air Max',
  price: 2500000,
)

Ở đây ProductCard không tự thay đổi name hoặc price.

Nếu dữ liệu thay đổi, Widget cha có thể truyền dữ liệu mới xuống.

4. Khi nào sử dụng StatelessWidget?

Sử dụng StatelessWidget khi Widget:

• Chỉ hiển thị dữ liệu.

• Không có State nội bộ cần thay đổi.

• Không cần gọi setState().

• Có thể nhận dữ liệu từ Widget cha thông qua constructor.

Một số ví dụ:

Logo
Text
Icon
ProductCard
ProfileHeader
CustomButton
CategoryItem
StaticInformation

5. StatefulWidget

StatefulWidget là Widget được sử dụng khi giao diện cần quản lý dữ liệu có thể thay đổi trong quá trình ứng dụng chạy.

Ví dụ phổ biến:

Counter
Checkbox
Switch
TextField
Form
Loading
Animation
Tab
Like Button
Shopping Cart

Một StatefulWidget  thường gồm hai phần:

StatefulWidget
      ↓
   State<T>
      ↓
    build()

Ví dụ:

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('$count'),

        ElevatedButton(
          onPressed: () {
            setState(() {
              count++;
            });
          },
          child: const Text('Increase'),
        ),
      ],
    );
  }
}

6. State

State là dữ liệu có thể thay đổi trong quá trình ứng dụng chạy và sự thay đổi đó có thể ảnh hưởng đến UI.

Ví dụ:

int count = 0;

Ban đầu:

count = 0

Người dùng nhấn nút:

count = 1

UI thay đổi:

0
↓
1

Đây chính là trường hợp cần sử dụng StatefulWidget.

7. setState()

setState() được sử dụng để thông báo cho Flutter rằng State đã thay đổi và UI cần được rebuild.

Ví dụ:

setState(() {
  count++;
});

Quá trình hoạt động:

User click
    ↓
setState()
    ↓
State thay đổi
    ↓
build() được gọi lại
    ↓
UI cập nhật

Nếu chỉ viết:

count++;

Flutter không được thông báo rằng UI cần rebuild theo cơ chế StatefulWidget.

8. Cấu trúc StatefulWidget

Một StatefulWidget có cấu trúc khác StatelessWidget.

StatelessWidget

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

  @override
  Widget build(BuildContext context) {
    return const Text('Hello');
  }
}

Chỉ cần một class.

StatefulWidget

class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return const Text('Hello');
  }
}

Thông thường sẽ có hai class:

MyWidget
    ↓
State<MyWidget>
    ↓
_MyWidgetState

Class StatefulWidget đại diện cho cấu hình của Widget.

Class State lưu trữ dữ liệu có thể thay đổi và xây dựng UI.

9. So sánh StatelessWidget và StatefulWidget

Tiêu chí StatelessWidget StatefulWidget
Có State nội bộ Không Có
State thay đổi Không quản lý Có
setState() Không Có
Số class cơ bản 1 2
Có build() Có Có
UI có thể thay đổi Có, khi nhận dữ liệu mới Có, dựa trên State
Độ phức tạp Thấp hơn Cao hơn
Sử dụng UI không cần State nội bộ UI cần State thay đổi

10. Một điểm dễ nhầm

StatelessWidget không có nghĩa là UI không bao giờ thay đổi.

Ví dụ:

class UserName extends StatelessWidget {
  final String name;

  const UserName({
    super.key,
    required this.name,
  });

  @override
  Widget build(BuildContext context) {
    return Text(name);
  }
}

Widget này vẫn có thể hiển thị dữ liệu khác:

UserName(name: 'Tien')

sau đó Widget cha rebuild và truyền:

UserName(name: 'Minh')

UI sẽ thay đổi từ:

Tien

thành:

Minh

Nhưng bản thân UserName không tự quản lý State thay đổi.

Vì vậy, cách hiểu chính xác là:

StatelessWidget không có State nội bộ có thể thay đổi.

11. StatefulWidget không có nghĩa là toàn bộ Widget đều thay đổi

Khi State thay đổi:

setState(() {
  count++;
});

Flutter gọi lại build() của State.

Flutter không đơn giản là xóa toàn bộ ứng dụng và vẽ lại từ đầu.

Flutter sử dụng Widget Tree, Element Tree và RenderObject Tree để xác định những phần cần được cập nhật.

12. Ví dụ thực tế

Nút Like

Nếu chỉ hiển thị một nút:

ElevatedButton(
  onPressed: () {},
  child: const Text('Like'),
)

Không nhất thiết cần StatefulWidget.

Nhưng nếu muốn khi nhấn nút thì số Like tăng:

Like: 0
     ↓
User click
     ↓
Like: 1

thì cần State để lưu số Like:

int likes = 0;

và cập nhật:

setState(() {
  likes++;
});

Trường hợp này phù hợp với StatefulWidget.

13. Ví dụ thực tế trong ứng dụng thương mại điện tử

StatelessWidget

ProductCard:

ProductCard
├── Image
├── Product Name
├── Price
└── Rating

Nếu Widget chỉ nhận thông tin sản phẩm và hiển thị chúng thì có thể sử dụng StatelessWidget.

StatefulWidget

QuantitySelector:

Quantity: 1

        [ - ]  1  [ + ]

Khi người dùng nhấn +:

1 → 2 → 3 → 4

Quantity thay đổi liên tục.

Do đó Widget có thể cần State:

int quantity = 1;

và:

setState(() {
  quantity++;
});

14. Lifecycle cơ bản của StatefulWidget

StatefulWidget có vòng đời khác với StatelessWidget.

Một số phương thức quan trọng:

createState()
    ↓
initState()
    ↓
build()
    ↓
setState()
    ↓
build()
    ↓
dispose()

initState()

Được gọi một lần khi State được tạo.

@override
void initState() {
  super.initState();

  // Khởi tạo
}

Thường dùng để:

  • Khởi tạo Controller.
  • Đăng ký Listener.
  • Khởi tạo dữ liệu.
  • Thực hiện các thao tác cần chạy một lần.

dispose()

Được gọi khi State không còn được sử dụng.

@override
void dispose() {
  controller.dispose();

  super.dispose();
}

Thường dùng để giải phóng tài nguyên như:

  • Controller.
  • AnimationController.
  • StreamSubscription.
  • Listener.

15. Quy tắc lựa chọn

Có thể sử dụng quy tắc đơn giản:

Widget có State nội bộ thay đổi?
        ↓
       Không
        ↓
StatelessWidget

Nếu:

Widget có State nội bộ thay đổi?
        ↓
       Có
        ↓
StatefulWidget

Ví dụ:

Text
→ StatelessWidget

ProductCard
→ Thường là StatelessWidget

Counter
→ StatefulWidget

Checkbox
→ StatefulWidget

Switch
→ StatefulWidget

Form
→ Thường cần StatefulWidget hoặc cơ chế quản lý State khác

16. StatelessWidget và StatefulWidget trong kiến trúc ứng dụng

Trong ứng dụng thực tế, không phải mọi Widget có dữ liệu thay đổi đều nhất thiết phải tự quản lý State bằng StatefulWidget.

Ví dụ ứng dụng lớn có thể sử dụng:

Provider
Riverpod
Bloc
Cubit
GetX

để quản lý State bên ngoài Widget.

Khi đó nhiều Widget vẫn có thể là StatelessWidget nhưng UI vẫn thay đổi khi State Management cung cấp dữ liệu mới.

Vì vậy:

StatefulWidget là cách quản lý State cục bộ cơ bản của Flutter, không phải cách duy nhất để quản lý State trong ứng dụng.

17. Tóm tắt

StatelessWidget
│
├── Không quản lý State nội bộ
├── Không dùng setState()
├── Đơn giản
├── Phù hợp với UI không cần State nội bộ
└── Có thể nhận dữ liệu từ Widget cha


StatefulWidget
│
├── Có State
├── State có thể thay đổi
├── Có State class
├── Có thể sử dụng setState()
├── build() được gọi lại khi State thay đổi
└── Phù hợp với UI có trạng thái thay đổi

Cách nhớ nhanh

StatelessWidget = UI không có State nội bộ cần thay đổi.

StatefulWidget = UI có State nội bộ thay đổi trong quá trình chạy.

Ví dụ dễ nhớ nhất:

Text "Hello"
→ StatelessWidget

Counter 0 → 1 → 2 → 3
→ StatefulWidget

Bài viết khác

Flutter Layout & Responsive UI

Flutter Layout & Responsive UI 1. Flutter Layout Layout trong Flutter là quá trình xác định kích thước và vị trí của các Widget trên màn hình. Flutter sử dụng hệ thống Widget để xây dựng giao diện. Các Widget Layout quyết định: Widget nằm ở đâu. Widget có kích thước bao nhiêu. Các Widget […]

Flutter Widget Fundamentals

Flutter Widget Fundamentals 1. Widget Trong Flutter, Widget là thành phần cơ bản dùng để xây dựng giao diện người dùng (UI). Có thể hiểu đơn giản: Mọi thành phần xuất hiện trên giao diện Flutter đều được xây dựng từ Widget. Ví dụ: Text → hiển thị văn bản. Image → hiển thị hình […]

Isolate & Concurrency

Isolate & Concurrency Isolate và Concurrency (đồng thời) là cơ chế trong Dart dùng để thực hiện nhiều công việc mà không làm một công việc phải chờ hoàn thành hoàn toàn mới có thể xử lý công việc khác. Lý thuyết 1. Concurrency Concurrency (xử lý đồng thời) là khả năng chương trình quản […]

Null Safety trong Dart

Null Safety là cơ chế phân biệt rõ ràng giữa kiểu dữ liệu có thể chứa null (Nullable) và kiểu dữ liệu không thể chứa null (Non-nullable) ngay ở mức ngôn ngữ lập trình, giúp trình biên dịch phát hiện và bắt lỗi liên quan đến null ngay trong quá trình viết mã thay vì […]

Test Scenario(Kịch bản kiểm thử)

1. Lời mở đầu và vị trí chiến lược của Test Scenario trong quy trình kiểm thử: Trong bức tranh tổng thể của vòng đời kiểm thử phần mềm (STLC), sau khi đã hoàn thành việc phân tích yêu cầu và lập kế hoạch kiểm thử (Test Plan), công việc tiếp theo mang tính chất […]

Dart OOP

OOP là từ viết tắt của cụm từ Object Oriented Programming. Nó có nghĩa là lập trình hướng đối tượng. Đây là một phương pháp lập trình dựa trên những khái niệm về đối tượng và lớp. OOP thường tập trung vào những đối tượng thao tác hơn là tập trung vào logic để có […]

Leave a Reply

Your email address will not be published. Required fields are marked *