Trong số các loại widget phổ biến nhất, StatelessWidget và StatefulWidget giữ vai trò đặc biệt quan trọng. Việc phân biệt hai loại widget này không chỉ giúp lập trình viên viết mã đúng mà còn ảnh hưởng đến khả năng tổ chức, bảo trì và tối ưu hiệu năng của ứng dụng. StatelessWidget phù hợp với những thành phần giao diện không cần tự quản lý trạng thái thay đổi, trong khi StatefulWidget được sử dụng khi giao diện cần phản ứng với dữ liệu hoặc sự kiện thay đổi trong quá trình chạy.
1. StatelessWidget là gì?
StatelessWidget là widget không có trạng thái nội bộ có thể thay đổi. Nó thường nhận dữ liệu thông qua hàm khởi tạo và sử dụng dữ liệu đó để tạo giao diện trong phương thức build().
Một ví dụ đơn giản:
class Greeting extends StatelessWidget {
final String name;
const Greeting({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Text('Xin chào, $name!');
}
}
Trong ví dụ trên, Greeting không tự thay đổi giá trị name. Nếu muốn hiển thị lời chào khác, widget cha phải tạo lại Greeting với một giá trị mới.
Đặc điểm của StatelessWidget
Một StatelessWidget thường có các đặc điểm sau:
- Không duy trì trạng thái thay đổi nội bộ: Các thuộc tính thường được khai báo dưới dạng final.
- Giao diện phụ thuộc vào dữ liệu đầu vào: Cùng một tập dữ liệu đầu vào thường tạo ra cùng một giao diện.
- Không sử dụng setState(): Vì widget không sở hữu đối tượng State để cập nhật.
- Phù hợp với thành phần giao diện tĩnh hoặc mang tính trình bày.
- Dễ kiểm thử và tái sử dụng do logic đơn giản, ít phụ thuộc vào vòng đời.
Ví dụ sử dụng StatelessWidget
Các trường hợp phổ biến gồm:
Tiêu đề màn hình.
- Biểu tượng hoặc nhãn văn bản.
- Thẻ hiển thị thông tin sản phẩm.
- Nút tùy chỉnh nhận callback từ widget cha.
- Thành phần bố cục chỉ hiển thị dữ liệu được truyền vào.
Ví dụ một thẻ sản phẩm:
class ProductCard extends StatelessWidget {
final String title;
final double price;
const ProductCard({
super.key,
required this.title,
required this.price,
});
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(title),
subtitle: Text('${price.toStringAsFixed(0)} đồng'),
),
);
}
}
ProductCard chỉ hiển thị dữ liệu nhận được. Việc sản phẩm được thêm vào giỏ hàng hay giá thay đổi nên được xử lý ở một lớp quản lý trạng thái hoặc widget cha, thay vì đặt trực tiếp trong widget trình bày nếu điều đó không cần thiết.
2. StatefulWidget là gì?
StatefulWidget là widget có thể thay đổi trạng thái trong suốt vòng đời của nó. Tuy nhiên, cần lưu ý rằng bản thân StatefulWidget thường chỉ là một đối tượng cấu hình bất biến. Trạng thái có thể thay đổi được lưu trong một đối tượng riêng kế thừa từ lớp State.
Ví dụ bộ đếm:
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() {
setState(() {
count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Số lần: $count'),
ElevatedButton(
onPressed: increment,
child: const Text('Tăng'),
),
],
);
}
}
Mỗi khi người dùng nhấn nút, setState() được gọi. Flutter nhận biết rằng trạng thái đã thay đổi và yêu cầu framework chạy lại build() để tạo giao diện mới với giá trị count cập nhật.
Đặc điểm của StatefulWidget
StatefulWidget có các đặc điểm chính:
- Có trạng thái thay đổi theo thời gian.
- Tách widget cấu hình khỏi đối tượng State.
- Sử dụng setState() để thông báo cho Flutter rằng giao diện cần được cập nhật.
- Có vòng đời phức tạp hơn, bao gồm các phương thức như initState(), didChangeDependencies(), didUpdateWidget() và dispose().
- Phù hợp với tương tác người dùng, animation, bộ điều khiển nhập liệu và dữ liệu bất đồng bộ.
Vòng đời cơ bản của StatefulWidget
Một số phương thức vòng đời thường gặp gồm:
- createState(): Tạo đối tượng trạng thái tương ứng.
- initState(): Được gọi một lần khi trạng thái được khởi tạo. Đây là nơi phù hợp để tạo controller, đăng ký listener hoặc khởi động tác vụ ban đầu.
- didChangeDependencies(): Được gọi khi một dependency của widget thay đổi.
- build(): Tạo giao diện dựa trên trạng thái hiện tại.
- didUpdateWidget(): Được gọi khi widget cha cung cấp cấu hình mới cho cùng một vị trí trong cây widget.
- dispose(): Giải phóng tài nguyên như TextEditingController, AnimationController, stream subscription hoặc listener.
Việc hiểu vòng đời giúp tránh lỗi rò rỉ tài nguyên và các hành vi không mong muốn trong ứng dụng.
3. Những điểm giống nhau
Dù có sự khác biệt về trạng thái, StatelessWidget và StatefulWidget vẫn có nhiều điểm chung:
- Đều là thành phần cơ bản để xây dựng giao diện Flutter.
- Đều sử dụng phương thức build() để mô tả giao diện.
- Đều có thể nhận tham số từ widget cha.
- Đều có thể chứa widget con và tạo nên cây widget.
- Đều có thể được tái sử dụng trong nhiều màn hình hoặc ngữ cảnh khác nhau.
- Đều nên tập trung vào một trách nhiệm rõ ràng để mã nguồn dễ bảo trì.
Ngoài ra, cả hai loại widget đều không nên được hiểu là các phần tử giao diện thực tế tồn tại vĩnh viễn trên màn hình. Flutter có thể tạo lại các đối tượng widget nhiều lần vì widget được xem như các đối tượng cấu hình nhẹ. Điều cần duy trì lâu dài đối với StatefulWidget là đối tượng State tương ứng.
4. Những điểm khác nhau quan trọng
| Tiêu chí | StatelessWidget | StatefulWidget |
|---|---|---|
| Trạng thái nội bộ | Không có trạng thái thay đổi nội bộ | Có đối tượng State lưu trạng thái |
| Cập nhật giao diện | Thường do widget cha xây dựng lại với dữ liệu mới | Có thể tự yêu cầu cập nhật bằng setState() |
| Độ phức tạp | Đơn giản hơn | Phức tạp hơn do có vòng đời |
| Tài nguyên cần giải phóng | Thường ít hoặc không có | Có thể cần giải phóng controller, listener, stream |
| Tình huống phù hợp | Hiển thị dữ liệu, bố cục, thành phần trình bày | Tương tác, animation, nhập liệu, dữ liệu thay đổi |
| Khả năng kiểm thử | Thường dễ kiểm thử hơn | Cần kiểm thử thêm vòng đời và thay đổi trạng thái |
| Ví dụ | Text, Icon, thẻ thông tin tùy chỉnh | Bộ đếm, form, checkbox có logic nội bộ |
5. Ưu điểm và hạn chế của StatelessWidget
Ưu điểm
- Đơn giản: Cấu trúc dễ đọc và dễ hiểu.
- Dễ tái sử dụng: Không phụ thuộc vào trạng thái nội bộ.
- Dễ kiểm thử: Có thể kiểm tra đầu vào và giao diện đầu ra một cách rõ ràng.
- Ít rủi ro về vòng đời: Thường không cần hủy controller hoặc listener.
- Khuyến khích kiến trúc rõ ràng: Trạng thái được quản lý ở nơi phù hợp hơn, chẳng hạn widget cha hoặc lớp quản lý trạng thái.
Hạn chế
- Không thể tự lưu và cập nhật trạng thái nội bộ.
- Không phù hợp với các giao diện có tương tác phức tạp nếu không kết hợp với một nguồn trạng thái bên ngoài.
- Có thể cần nhận nhiều dữ liệu và callback từ widget cha nếu phần logic không được tổ chức tốt.
6. Ưu điểm và hạn chế của StatefulWidget
Ưu điểm
- Có thể phản ứng trực tiếp với thao tác người dùng.
- Phù hợp với dữ liệu thay đổi trong thời gian chạy.
- Có thể quản lý các controller, animation và subscription gắn với vòng đời widget.
- Thích hợp cho các thành phần nhỏ có logic giao diện độc lập.
Hạn chế
- Cấu trúc phức tạp hơn do phải tách lớp widget và lớp state.
- Dễ phát sinh lỗi nếu gọi setState() sau khi widget đã bị hủy.
- Có nguy cơ rò rỉ tài nguyên nếu quên gọi dispose().
- Một StatefulWidget quá lớn có thể trở thành nơi tập trung quá nhiều logic, khó đọc và khó kiểm thử.
- Nếu quản lý trạng thái không hợp lý, giao diện có thể bị xây dựng lại nhiều hơn cần thiết.
7. Khi nào nên dùng StatelessWidget?
Nên ưu tiên StatelessWidget khi:
- Giao diện chỉ phụ thuộc vào các tham số đầu vào.
- Thành phần không cần phản ứng với thao tác nội bộ.
- Trạng thái đã được quản lý bởi widget cha hoặc giải pháp quản lý trạng thái bên ngoài.
- Muốn tạo một thành phần trình bày có thể tái sử dụng.
- Muốn giảm phạm vi trách nhiệm của widget và làm mã nguồn dễ kiểm thử hơn.
Ví dụ, một nút tùy chỉnh không cần tự biết khi nào được nhấn có thể là StatelessWidget; nó chỉ nhận một callback onPressed từ bên ngoài. Khi người dùng nhấn nút, widget cha quyết định phải thay đổi dữ liệu hay giao diện như thế nào.
8. Khi nào nên dùng StatefulWidget?
Nên dùng StatefulWidget khi:
- Có dữ liệu nội bộ thay đổi và thay đổi đó ảnh hưởng trực tiếp đến giao diện.
- Widget cần xử lý trạng thái mở rộng hoặc thu gọn.
- Cần điều khiển animation.
- Cần sử dụng TextEditingController, ScrollController hoặc các controller khác.
- Cần theo dõi vòng đời để khởi tạo hoặc giải phóng tài nguyên.
- Cần quản lý trạng thái tạm thời, chẳng hạn giá trị đang nhập trong một form hoặc trạng thái hiển thị lỗi của một trường dữ liệu.
Tuy nhiên, không phải mọi dữ liệu thay đổi đều phải đặt vào một StatefulWidget. Nếu trạng thái được chia sẻ giữa nhiều màn hình hoặc nhiều thành phần, một giải pháp quản lý trạng thái bên ngoài có thể phù hợp hơn.
Kết luận
StatelessWidget và StatefulWidget là hai khối xây dựng quan trọng trong Flutter. StatelessWidget phù hợp với những thành phần giao diện không tự duy trì trạng thái thay đổi, có cấu trúc đơn giản, dễ tái sử dụng và dễ kiểm thử. Trong khi đó, StatefulWidget phù hợp với những thành phần cần phản ứng với dữ liệu, sự kiện người dùng, animation hoặc các thay đổi diễn ra trong vòng đời ứng dụng.








Khoá học lập trình game con rắn cho trẻ em




