StatefulWidget: nó là gì, vòng đời và nguyên lý hoạt động

Tác giả: IT Sectr Đã đăng: 2026-07-01 Thời gian đọc: 9 phút

StatefulWidget là một widget Flutter có trạng thái thay đổi được, cho phép UI phản ứng với các hành động của người dùng, sự kiện bất đồng bộ và luồng dữ liệu. Theo tài liệu chính thức của Flutter (Flutter.dev, 2026), StatefulWidget được sử dụng cho tất cả các thành phần tương tác của ứng dụng: biểu mẫu nhập liệu, hoạt ảnh, hộp kiểm, công tắc và màn hình tải dữ liệu từ mạng. Không giống như StatelessWidget, nó tạo một đối tượng State riêng biệt tồn tại trong suốt vòng đời của nó và có thể được xây dựng lại mà không cần tạo lại chính widget đó.

Những điểm chính

  • StatefulWidget là widget có thể thay đổi trạng thái trong thời gian chạy, kích hoạt việc xây dựng lại UI thông qua setState
  • Vòng đời — StatefulWidget trải qua các giai đoạn createState, initState, didChangeDependencies, build, didUpdateWidget, dispose
  • Đối tượng State là một đối tượng riêng biệt lưu trữ trạng thái và tồn tại độc lập với widget trong suốt vòng đời của nó
  • setState là cách hợp pháp duy nhất để thông báo cho Flutter rằng widget cần được xây dựng lại sau khi thay đổi dữ liệu
  • Hiệu suất — việc sử dụng quá nhiều StatefulWidget làm tăng mức tiêu thụ bộ nhớ và thời gian kết xuất

StatefulWidget là gì?

StatefulWidget là một lớp Flutter có thể thay đổi trạng thái để đáp ứng với các hành động của người dùng, sự kiện hệ thống hoặc các hoạt động bất đồng bộ. Không giống như StatelessWidget, StatefulWidget không được kết xuất trực tiếp — nó tạo một đối tượng State chịu trách nhiệm kết xuất. Sự phân chia thành hai lớp (Widget và State) cho phép Flutter xây dựng lại UI mà không cần tạo lại chính widget đó, mang lại lợi thế hiệu suất đáng kể trong các bản cập nhật thường xuyên.

Kiến trúc của StatefulWidget tuân theo mẫu “phân tách giữa thay đổi được và không thay đổi được”: bản thân widget vẫn không thay đổi (giống như StatelessWidget), trong khi tất cả trạng thái thay đổi được lưu trữ trong một đối tượng State riêng biệt. Điều này cho phép Flutter tái sử dụng widget bằng cách so sánh chúng theo loại và Key, đồng thời bảo toàn trạng thái thực tế giữa các lần xây dựng lại.

Theo Google (Flutter Architectural Overview, 2026), StatefulWidget là tối ưu cho các kịch bản mà trạng thái thay đổi nhiều hơn một lần trong suốt vòng đời của widget: trường văn bản, hoạt ảnh, bộ đếm thời gian, luồng dữ liệu, tải bất đồng bộ. Đối với khởi tạo một lần, StatelessWidget là đủ.

Khi nào cần StatefulWidget

StatefulWidget là bắt buộc khi widget cần phản ứng với các sự kiện bên ngoài: nhấp nút, hoàn thành yêu cầu HTTP, cập nhật dữ liệu cơ sở dữ liệu, đăng ký WebSocket. Nó cũng cần thiết cho widget có hoạt ảnh, trường văn bản có bộ điều khiển và các thành phần quản lý tiêu điểm. Nếu một widget chỉ hiển thị dữ liệu và không tạo ra sự kiện, hãy sử dụng StatelessWidget.

Cấu trúc bên trong

StatefulWidget bao gồm hai lớp: StatefulWidget (nhẹ, không thay đổi) và State (nặng, có thể thay đổi). Framework tạo State thông qua phương thức createState(), được gọi một lần khi chèn vào cây. State nhận được tham chiếu đến widget thông qua thuộc tính widget và có thể truy cập các trường của nó tại bất kỳ thời điểm nào trong vòng đời.

Vòng đời của StatefulWidget

Vòng đời của StatefulWidget bao gồm sáu giai đoạn chính, mỗi giai đoạn cung cấp một phương thức có thể ghi đè để thực hiện các tác vụ cụ thể. Hiểu các giai đoạn này rất quan trọng để quản lý tài nguyên đúng cách và tránh rò rỉ bộ nhớ.

createState

createState là phương thức đầu tiên của vòng đời, được gọi khi StatefulWidget được chèn vào cây. Nó phải trả về một phiên bản State mới liên kết với widget này. Phương thức này được gọi chính xác một lần trong suốt vòng đời của phần tử. Điều quan trọng là không thực hiện các hoạt động nặng ở đây — createState nên nhẹ nhất có thể.

initState

initState được gọi ngay sau khi State được tạo, trước khi xây dựng UI lần đầu tiên. Ở đây thực hiện: khởi tạo bộ điều khiển (TextEditingController, AnimationController), đăng ký luồng dữ liệu (StreamSubscription), thiết lập bộ đếm thời gian và khởi tạo trường ban đầu. Theo tài liệu Flutter (Flutter.dev, 2026), không thể gọi BuildContext.of() trong initState — cây chưa được gắn kết hoàn toàn.

didChangeDependencies

didChangeDependencies được gọi sau initState và mỗi khi các phụ thuộc InheritedWidget thay đổi. Đây là nơi thích hợp để gọi MediaQuery.of(context) hoặc đăng ký Theme — các giá trị có thể thay đổi trong thời gian chạy ứng dụng. Nếu widget sử dụng InheritedWidget, logic khởi tạo nên ở đây, không phải trong initState.

build và didUpdateWidget

build là phương thức chính trả về cây widget. Nó được gọi sau initState, sau didChangeDependencies và sau mỗi lần setState. didUpdateWidget được gọi khi cha mẹ xây dựng lại và truyền StatefulWidget với các tham số mới. Ở đây có thể so sánh các trường widget cũ và mới, và nếu cần, cập nhật trạng thái.

dispose

dispose là giai đoạn cuối cùng của vòng đời. Tất cả tài nguyên được giải phóng ở đây: hủy đăng ký luồng, xóa bộ điều khiển, hủy bộ đếm thời gian. Không gọi dispose dẫn đến rò rỉ bộ nhớ. Sau dispose, State được coi là đã chết — gọi setState bên trong nó sẽ ném ra ngoại lệ.

StatefulWidget hoạt động như thế nào?

Cơ chế hoạt động của StatefulWidget dựa trên sự phối hợp của ba thực thể: Widget (mô tả nhẹ), Element (lớp trung gian) và State (lưu trữ dữ liệu). Khi Flutter gặp StatefulWidget trong mô tả, nó tạo StatefulElement, gọi createState và lưu trữ tham chiếu đến đối tượng State. Khi cha mẹ xây dựng lại, Flutter so sánh widget mới với Element hiện tại — nếu loại và Key khớp, Element được cập nhật và State vẫn giữ nguyên.

Trạng thái chỉ được thay đổi thông qua lệnh gọi setState, thông báo cho framework rằng cần xây dựng lại. Điều quan trọng là hiểu: setState không tự động thay đổi trạng thái — nó chỉ đánh dấu widget là “bẩn”. Nhà phát triển tự cập nhật các trường State trong callback được truyền cho setState. Sau khi callback hoàn thành, Flutter gọi build và cập nhật UI.

Theo nhóm Dart/Flutter (Dart Language Specification, 2026), sự phân tách này đảm bảo rằng tất cả các thay đổi trạng thái xảy ra đồng bộ trước khi build được gọi, loại bỏ tình huống UI hiển thị dữ liệu được cập nhật một phần. Đây là cơ chế nhất quán giao diện chính trong Flutter.

Ví dụ mã Dart

Hãy xem một StatefulWidget đơn giản — bộ đếm số lần nhấp nút. Nó minh họa mẫu cơ bản: tạo State, khởi tạo trường trong initState, thay đổi qua setState:

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

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

Một ví dụ với tải dữ liệu bất đồng bộ và quản lý vòng đời. StatefulWidget tải dữ liệu từ mạng và hiển thị trạng thái tải:

dart
class UserProfilePage extends StatefulWidget {
  final String userId;
  const UserProfilePage({super.key, required this.userId});

  @override
  State<UserProfilePage> createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  UserModel? _user;
  bool _isLoading = true;

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

  Future<void> _loadUser() async {
    final user = await UserService.fetchUser(widget.userId);
    setState(() {
      _user = user;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const CircularProgressIndicator();
    return Text('Hello, ${_user!.name}');
  }
}

Trong ví dụ thứ hai, điều quan trọng cần lưu ý: initState bắt đầu một hoạt động bất đồng bộ, nhưng bản thân phương thức không phải là bất đồng bộ. Tính bất đồng bộ được thực hiện thông qua async/await bên trong một phương thức riêng _loadUser, phương thức này cập nhật trạng thái qua setState sau khi yêu cầu hoàn thành. Cách tiếp cận này đảm bảo widget hiển thị chính xác chỉ báo tải trước khi nhận được dữ liệu.

StatefulWidget vs StatelessWidget

Sự lựa chọn giữa StatefulWidget và StatelessWidget không chỉ là vấn đề có trạng thái hay không. StatefulWidget cung cấp một vòng đời đầy đủ với các phương thức initState, didChangeDependencies, didUpdateWidget và dispose, cần thiết để làm việc với bộ điều khiển, hoạt ảnh và luồng. StatelessWidget, mặt khác, không có các phương thức này và luôn nhẹ hơn cho framework.

Khuyến nghị của nhóm Flutter (Flutter docs, 2026) là giảm thiểu số lượng StatefulWidget trong ứng dụng bằng cách đưa trạng thái lên cao trong cây (State Hoisting) hoặc sử dụng các giải pháp quản lý trạng thái (Riverpod, Bloc, Provider). Mỗi StatefulWidget tạo một đối tượng State tồn tại cho đến khi phần tử bị xóa — càng nhiều widget như vậy, tải bộ nhớ càng cao.

Tiêu chíStatefulWidgetStatelessWidget
Trạng tháiCó thể thay đổiKhông thay đổi
Vòng đời6 giai đoạnChỉ build
Đối tượng StateĐược tạo riêngKhông cần
setStateCó sẵnKhông có sẵn
Đăng kýinitState/disposeKhông hỗ trợ
Trình tạo constHạn chếHỗ trợ đầy đủ
Tiêu thụ bộ nhớCao hơnThấp hơn

Hiệu suất và tối ưu hóa

StatefulWidget yêu cầu nhiều tài nguyên hơn StatelessWidget do cần tạo và duy trì một đối tượng State. Tuy nhiên, việc sử dụng đúng StatefulWidget không gây ra vấn đề hiệu suất nếu tuân theo một vài quy tắc. Thứ nhất, tránh lồng ghép sâu StatefulWidget — mỗi cấp độ thêm chi phí cho việc duyệt cây. Thứ hai, chia StatefulWidget phức tạp thành nhiều widget đơn giản, mỗi widget chịu trách nhiệm cho phần trạng thái của riêng mình.

Theo nghiên cứu Hiệu suất Flutter (Flutter.dev, tháng 2 năm 2026), nguyên nhân phổ biến nhất của việc giảm FPS là gọi setState trong widget cha mẹ làm xây dựng lại tất cả các widget con, bao gồm cả StatelessWidget không thay đổi hiển thị. Giải pháp là trích xuất phần có thể thay đổi của UI vào một StatefulWidget riêng biệt để setState chỉ xây dựng lại các widget tối thiểu cần thiết.

Sử dụng const bên trong State là một kỹ thuật quan trọng khác. Nếu các widget con được khai báo là const, Flutter sẽ không xây dựng lại chúng khi setState được gọi trong cha mẹ. Điều này giảm tải cho framework và giảm thời gian kết xuất khung hình.

Tránh setState thường xuyên

Mỗi lần gọi setState kích hoạt việc xây dựng lại toàn bộ widget. Nếu trạng thái thay đổi với tần suất cao (ví dụ: hoạt ảnh hoặc luồng dữ liệu), hãy cân nhắc sử dụng AnimatedBuilder, ValueListenableBuilder hoặc StreamBuilder thay vì gọi setState thủ công. Các widget này tối ưu hóa việc xây dựng lại, chỉ cập nhật phần UI thực sự đã thay đổi.

Lỗi thường gặp

Lỗi phổ biến đầu tiên với StatefulWidget là gọi setState sau dispose. Khi một widget bị xóa khỏi cây, State được coi là đã chết và bất kỳ lệnh gọi setState nào cũng ném ra ngoại lệ “setState called after dispose”. Điều này thường xảy ra nhất khi một hoạt động bất đồng bộ hoàn thành sau khi widget đã bị xóa. Giải pháp là kiểm tra cờ mounted trước khi gọi setState hoặc hủy các hoạt động bất đồng bộ trong dispose.

Lỗi thứ hai là thực hiện các tính toán nặng trong phương thức build. Vì build được gọi ở mỗi lần setState và mỗi lần cha mẹ xây dựng lại, tất cả các tính toán nên nhẹ nhất có thể. Nếu cần một hoạt động tốn nhiều tài nguyên, hãy di chuyển nó vào một Isolate riêng hoặc lưu kết quả vào bộ nhớ đệm trong trường State.

Lỗi thứ ba là không gọi super.initState()super.dispose(). Khi ghi đè các phương thức này, nhà phát triển phải gọi triển khai của cha mẹ. Nếu không, framework sẽ không thể quản lý đúng trạng thái của Element, dẫn đến các lỗi khó theo dõi.

Khuyến nghị để tránh lỗi

  • Luôn kiểm tra mounted trước setState trong callback bất đồng bộ
  • Đừng quên gọi super.initState()super.dispose()
  • Không thực hiện yêu cầu HTTP trực tiếp trong build — sử dụng initState
  • Hủy tất cả đăng ký trong dispose
  • Sử dụng số lượng StatefulWidget tối thiểu trong dự án của bạn

Câu hỏi thường gặp

StatefulWidget khác StatelessWidget như thế nào?

StatefulWidget có thể thay đổi trạng thái qua setState, có vòng đời (initState, dispose) và tạo một đối tượng State riêng biệt. StatelessWidget không thể thay đổi trạng thái và không có phương thức vòng đời — nó chỉ hiển thị dữ liệu được truyền vào.

createState được gọi bao nhiêu lần?

createState được gọi chính xác một lần cho mỗi phiên bản StatefulElement. Ngay cả khi cha mẹ xây dựng lại nhiều lần, miễn là loại và Key của widget không thay đổi, createState không được gọi — đối tượng State hiện có được sử dụng.

Điều gì xảy ra nếu không gọi dispose?

Tài nguyên sẽ không được giải phóng: bộ điều khiển tiếp tục chạy trong nền, đăng ký luồng vẫn hoạt động, bộ đếm thời gian không bị hủy. Điều này dẫn đến rò rỉ bộ nhớ và có thể gây ra lệnh gọi setState sau dispose, ném ra ngoại lệ.

StatefulWidget có thể là const không?

Có, trình tạo của StatefulWidget có thể là const. Tuy nhiên, điều này không mang lại lợi ích như đối với StatelessWidget — đối tượng State vẫn sẽ được tạo khi chèn lần đầu. const chỉ ảnh hưởng đến widget (lớp bọc nhẹ), không ảnh hưởng đến State.

Tại sao cần phương thức didUpdateWidget?

didUpdateWidget được gọi khi cha mẹ truyền StatefulWidget với các tham số mới. Điều này cần thiết để đồng bộ hóa trạng thái với dữ liệu mới — ví dụ: nếu userId trong tham số đã thay đổi, cần tải hồ sơ của người dùng mới.

Tổng kết

  • StatefulWidget là widget có trạng thái thay đổi được, sử dụng đối tượng State riêng để lưu trữ dữ liệu và quản lý vòng đời
  • Vòng đời bao gồm createState, initState, didChangeDependencies, build, didUpdateWidget và dispose, mỗi cái có mục đích riêng
  • setState là cách hợp pháp duy nhất để thông báo cho framework về sự thay đổi trạng thái, sau đó build tự động được gọi
  • mounted là cờ cần được kiểm tra trước khi gọi setState trong các hoạt động bất đồng bộ để tránh ngoại lệ sau dispose
  • Hiệu suất — StatefulWidget yêu cầu nhiều tài nguyên hơn StatelessWidget; khuyến nghị giảm thiểu số lượng bằng cách đưa trạng thái lên các lớp bên ngoài
  • Widget con const bên trong State giúp giảm lượng xây dựng lại khi gọi setState, cải thiện hiệu suất
  • Lựa chọn đúng — chỉ sử dụng StatefulWidget khi widget cần quản lý dữ liệu thay đổi được hoặc hoạt động bất đồng bộ

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm