State: nó là gì, quản lý trạng thái và nguyên lý hoạt động

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

State là đối tượng quản lý dữ liệu trung tâm trong Flutter, được liên kết với StatefulWidget và chịu trách nhiệm lưu trữ thông tin có thể thay đổi và xây dựng giao diện. Theo tài liệu chính thức của Flutter (Flutter.dev, 2026), State tồn tại trong suốt vòng đời của widget và tồn tại sau khi widget được xây dựng lại, đảm bảo tính nhất quán dữ liệu giữa các lần cập nhật UI. Không giống như widget, State có thể sửa đổi các trường của nó và kích hoạt xây dựng lại thông qua lệnh gọi setState.

Những điểm chính

  • State — một đối tượng lưu trữ dữ liệu có thể thay đổi của StatefulWidget và quản lý việc xây dựng lại của nó qua setState
  • Vòng đời — State trải qua initState, didChangeDependencies, build, didUpdateWidget và dispose, mỗi giai đoạn có mục đích rõ ràng
  • mounted — một cờ cho biết State vẫn còn trong cây widget và có thể gọi setState một cách an toàn
  • widget — một tham chiếu đến StatefulWidget được liên kết, có thể truy cập qua thuộc tính State để đọc tham số của cha
  • Cô lập — State bị cô lập khỏi các State khác; để trao đổi dữ liệu, sử dụng InheritedWidget hoặc công cụ quản lý trạng thái bên ngoài

State trong Flutter là gì?

State là một đối tượng trong kiến trúc Flutter lưu trữ dữ liệu có thể thay đổi của StatefulWidget và xác định cách dữ liệu này được hiển thị trong giao diện. Mỗi StatefulWidget, khi được chèn vào cây, tạo ra chính xác một đối tượng State thông qua phương thức createState. State tồn tại độc lập với widget: nếu cha xây dựng lại StatefulWidget với các tham số mới, State vẫn giữ nguyên và nhận widget đã cập nhật thông qua thuộc tính widget.

Theo Tổng quan Kiến trúc Flutter (Google, 2026), việc tách Widget và State là một quyết định kiến trúc có chủ đích cho phép framework tái sử dụng các phần tử cây. Widget (một mô tả nhẹ) có thể được tạo và hủy nhiều lần, nhưng State (một đối tượng nặng với dữ liệu) vẫn ở trong bộ nhớ miễn là phần tử còn trong cây. Điều này ngăn mất dữ liệu trong quá trình xây dựng lại thường xuyên của các widget cha.

State triển khai giao diện StatefulWidget thông qua generics: class _MyState extends State<MyWidget>. Generic liên kết State với một loại StatefulWidget cụ thể, cung cấp quyền truy cập an toàn về kiểu đến các trường của nó thông qua thuộc tính widget.

State được lưu trữ ở đâu?

Đối tượng State được lưu trữ trong StatefulElement — một lớp trung gian giữa Widget và RenderObject. StatefulElement tạo State thông qua createState, giữ một tham chiếu đến nó và chuyển State làm chủ sở hữu. Element chỉ bị hủy khi widget bị xóa khỏi cây — cho đến lúc đó, State tồn tại trong bộ nhớ.

Vòng đời của State

Vòng đời của State là tất định và bao gồm một chuỗi các lệnh gọi nghiêm ngặt. Hiểu trình tự này là nền tảng để quản lý tài nguyên đúng cách và ngăn rò rỉ bộ nhớ.

initState — khởi tạo

initState được gọi đầu tiên khi State được tạo. Trong phương thức này, các bộ điều khiển, đăng ký luồng, bộ đếm thời gian và giá trị ban đầu của các trường được khởi tạo. Bắt buộc phải gọi super.initState() ở dòng đầu tiên. Ở giai đoạn initState, cây widget chưa được gắn kết hoàn toàn, vì vậy các phương thức như MediaQuery.of(context) có thể hoạt động không chính xác.

didChangeDependencies

didChangeDependencies được gọi sau initState và mỗi khi các phụ thuộc InheritedWidget thay đổi. Đây là nơi, không phải trong initState, nên gọi MediaQuery.of(context) hoặc Theme.of(context), vì lúc này cây đã được gắn kết. Phương thức này cũng được gọi nếu widget di chuyển đến một ngữ cảnh khác nơi InheritedWidget cung cấp các giá trị khác.

build — xây dựng UI

build là phương thức chính của State trả về một cây widget. Nó được gọi sau initState, sau didChangeDependencies và sau mỗi setState. Phương thức build không được có tác dụng phụ — nó chỉ mô tả giao diện dựa trên các giá trị hiện tại của các trường State.

didUpdateWidget

didUpdateWidget được gọi khi cha xây dựng lại StatefulWidget với các tham số mới. State có quyền truy cập vào widget cũ thông qua oldWidget và có thể so sánh nó với widget mới. Nếu tham số đã thay đổi, có thể cập nhật trạng thái, tải dữ liệu mới hoặc khởi động lại hoạt ảnh.

dispose — giải phóng tài nguyên

dispose là phương thức cuối cùng nơi tất cả tài nguyên được giải phóng: bộ điều khiển, đăng ký, bộ đếm thời gian. Sau dispose, State được đánh dấu là đã chết: mounted trả về false, gọi setState sẽ ném ra ngoại lệ. Bắt buộc phải gọi super.dispose() ở dòng cuối cùng của phương thức.

Phương thứcKhi nào được gọiBắt buộc super
initStateKhi State được tạoCó, ở dòng đầu tiên
didChangeDependenciesSau initState và khi InheritedWidget thay đổi
buildSau initState, didChangeDependencies, setStateKhông
didUpdateWidgetKhi nhận widget mới từ cha
setStateKhi nhà phát triển gọiKhông
disposeKhi xóa khỏi câyCó, ở dòng cuối cùng

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

Cơ chế hoạt động của State dựa trên ba nguyên tắc chính: liên kết với Element, phản ứng thông qua setState và truy cập cha thông qua thuộc tính widget. Khi Flutter xây dựng cây phần tử và gặp StatefulElement, nó gọi createState của widget được liên kết. State được tạo được lưu trữ trong phần tử và tồn tại cho đến khi phần tử bị xóa.

Khi setState được gọi, State tự đánh dấu là dirty và lên lịch xây dựng lại cho khung hình tiếp theo. Quan trọng: setState không gọi build ngay lập tức — nó chỉ đăng ký nhu cầu xây dựng lại. Flutter thu thập tất cả các phần tử dirty trong khung hình hiện tại và xây dựng lại chúng theo lô, giúp tối ưu hiệu suất. Sau khi build được gọi, State trở lại trạng thái sạch.

Thuộc tính widget cho phép State đọc các tham số được truyền cho hàm tạo StatefulWidget. Vì StatefulWidget là bất biến (giống StatelessWidget), các trường của nó không thay đổi — khi tham số thay đổi, cha tạo một widget mới và State nhận nó thông qua didUpdateWidget. Điều này đảm bảo State luôn làm việc với dữ liệu cha hiện tại.

Ví dụ mã Dart

Ví dụ cơ bản về State với một trường được sửa đổi bởi bộ đếm thời gian. Minh họa initState, setState và dispose:

dart
class _TimerWidgetState extends State<TimerWidget> {
  int _seconds = 0;
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(
      const Duration(seconds: 1),
      (_) => setState(() => _seconds++),
    );
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text('$_seconds seconds elapsed');
  }
}

Ví dụ sử dụng thuộc tính widget để truy cập tham số cha và phản ứng với các thay đổi của chúng thông qua didUpdateWidget:

dart
class _GreetingState extends State<GreetingWidget> {
  String _displayName = '';

  @override
  void initState() {
    super.initState();
    _displayName = _formatName(widget.name);
  }

  @override
  void didUpdateWidget(GreetingWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.name != oldWidget.name) {
      setState(() {
        _displayName = _formatName(widget.name);
      });
    }
  }

  String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $_displayName!');
  }
}

Trong ví dụ thứ hai, State theo dõi các thay đổi của tham số đầu vào name và định dạng lại hiển thị chỉ khi có thay đổi thực sự. Nếu không có kiểm tra widget.name != oldWidget.name, phương thức sẽ được gọi trên mỗi lần cha xây dựng lại, ngay cả khi tên không thay đổi — công việc không cần thiết cho framework.

State vs StatefulWidget

State và StatefulWidget là hai lớp khác nhau trong kiến trúc Flutter thực hiện các vai trò khác nhau. StatefulWidget là một lớp bọc bất biến nhẹ mô tả cấu hình widget và tạo State. State là một đối tượng nặng lưu trữ dữ liệu có thể thay đổi, quản lý đăng ký và xây dựng UI. Sự tách biệt này cho phép Flutter hủy và tạo widget mà không mất trạng thái.

Tất cả các trường của StatefulWidget phải là final và được đặt trong hàm tạo — chúng không thay đổi sau khi tạo. State, mặt khác, có thể sửa đổi các trường của nó bất kỳ lúc nào, nhưng tất cả các thay đổi phải được thực hiện trước lệnh gọi setState để Flutter biết về nhu cầu xây dựng lại. Đây là sự khác biệt chính: StatefulWidget là “những gì để hiển thị”, State là “làm thế nào để hiển thị và dữ liệu nào để sử dụng”.

Theo phân tích mã nguồn Flutter (Flutter SDK, 2026), StatefulWidget chỉ chứa một trường bắt buộc — createState, trong khi State có quyền truy cập vào BuildContext, có thể đăng ký luồng, quản lý hoạt ảnh và bộ điều khiển. Nên giữ StatefulWidget đơn giản nhất có thể, chuyển tất cả logic sang State.

Tại sao StatefulWidget không thể là State?

Sự tách biệt Widget và State là một quyết định kiến trúc đảm bảo tính bất biến của cấu hình. Nếu StatefulWidget tự lưu trữ trạng thái, trạng thái sẽ bị mất trên mỗi lần cha xây dựng lại. Bằng cách chuyển trạng thái sang một đối tượng riêng biệt, Flutter đảm bảo dữ liệu tồn tại sau các lần xây dựng lại, trong khi các widget vẫn nhẹ và có thể so sánh được.

Quản lý trạng thái giữa các widget

Đối tượng State bị cô lập — nó không có quyền truy cập trực tiếp vào State của các widget khác. Để trao đổi dữ liệu giữa các widget, sử dụng InheritedWidget hoặc các công cụ quản lý trạng thái bên ngoài: Provider, Riverpod, Bloc, Redux. Mỗi cách tiếp cận giải quyết vấn đề khác nhau: InheritedWidget hoạt động thông qua cây widget, Provider thông qua vùng chứa DI, Bloc thông qua luồng sự kiện.

Việc chọn công cụ phụ thuộc vào quy mô dự án. Đối với ứng dụng nhỏ, InheritedWidget và State cục bộ là đủ. Đối với dự án vừa và lớn, nên sử dụng Riverpod hoặc Bloc — chúng đảm bảo khả năng kiểm thử, khả năng dự đoán và tách logic khỏi UI. State sau đó chỉ được sử dụng cho dữ liệu cục bộ của widget (tiêu điểm, cuộn, hoạt ảnh).

Theo Khảo sát Cộng đồng Flutter 2025 (Flutter Foundation, tháng 12 năm 2025), Riverpod là giải pháp quản lý trạng thái phổ biến nhất trong các dự án mới (38%), tiếp theo là Bloc (31%) và Provider (22%). Cả ba công cụ đều tương thích với State và không yêu cầu từ bỏ vòng đời tiêu chuẩn.

Trạng thái cục bộ vs toàn cục

  • Cục bộ — trong State của một widget cụ thể (vị trí cuộn, trạng thái tiêu điểm)
  • Toàn cục — trong kho lưu trữ bên ngoài (dữ liệu người dùng, cài đặt, bộ nhớ đệm)
  • Quy tắc: nếu dữ liệu chỉ được sử dụng bởi một widget — lưu trữ trong State
  • Nếu dữ liệu được sử dụng bởi 2+ widget — chuyển sang Riverpod/Bloc/Provider

Lỗi thường gặp

Lỗi đầu tiên là quên kiểm tra mounted trước setState trong callback bất đồng bộ. Khi một widget bị xóa khỏi cây (ví dụ: người dùng đã rời khỏi màn hình), nhưng một hoạt động bất đồng bộ (yêu cầu HTTP) vẫn đang chạy, sau khi hoàn thành State đã chết. Gọi setState trong State đã chết sẽ ném ra ngoại lệ. Kiểm tra if (mounted) setState(...) giải quyết vấn đề.

Lỗi thứ hai là khởi tạo các phụ thuộc InheritedWidget trong initState thay vì didChangeDependencies. Trong initState, ngữ cảnh chưa được gắn kết, vì vậy MediaQuery.of(context) sẽ ném ra ngoại lệ. Tất cả các phụ thuộc InheritedWidget nên được thiết lập trong didChangeDependencies hoặc trong build.

Lỗi thứ ba là thay đổi các trường mà không gọi setState. Nếu nhà phát triển thay đổi trường State mà không có setState, Flutter sẽ không biết về thay đổi và UI sẽ không được cập nhật. Ví dụ: _list.add(item) mà không có setState((){}) sau đó sẽ sửa đổi danh sách, nhưng màn hình sẽ vẫn như cũ.

Kiểm tra mounted trước setState

Một mẫu an toàn cho các hoạt động bất đồng bộ trong State:

dart
Future<void> _fetchData() async {
  final data = await ApiService.fetch();
  if (mounted) {
    setState(() => _data = data);
  }
}

Kiểm tra mounted đảm bảo setState chỉ được gọi trên State còn sống, ngăn ngoại lệ “setState called after dispose”.

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

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

StatefulWidget là một cấu hình widget bất biến, trong khi State là một đối tượng có thể thay đổi lưu trữ dữ liệu và quản lý vòng đời. Widget có thể được tạo lại, State thì không. StatefulWidget tạo State thông qua createState.

Có bao nhiêu đối tượng State được tạo cho một StatefulWidget?

Chính xác là một. Phương thức createState được gọi một lần khi StatefulWidget lần đầu tiên được chèn vào cây. Ngay cả khi cha xây dựng lại nhiều lần, đối tượng State vẫn giữ nguyên cho đến khi loại hoặc Key của widget thay đổi.

mounted trong State là gì?

mounted là một cờ boolean cho biết State có trong cây widget hay không. Sau khi gọi dispose, mounted trở thành false. Nó được sử dụng để kiểm tra trước setState trong các callback bất đồng bộ để tránh ngoại lệ.

Có thể sử dụng State mà không có StatefulWidget không?

Không. State luôn được gắn với một StatefulWidget cụ thể thông qua generics: State<T extends StatefulWidget>. Tạo State trực tiếp, không có liên kết với widget, là không thể về mặt kiến trúc.

Điều gì xảy ra khi gọi setState trong dispose?

Một ngoại lệ được ném ra: “setState called after dispose”. Sau dispose, State được coi là đã chết và mọi nỗ lực xây dựng lại UI thông qua setState đều bị cấm. Giải pháp là kiểm tra mounted trước mỗi setState.

Tóm tắt

  • State — đối tượng quản lý dữ liệu StatefulWidget lưu trữ các trường có thể thay đổi và kích hoạt xây dựng lại UI thông qua setState
  • Vòng đời bao gồm các phương thức bắt buộc initState, didChangeDependencies, build, didUpdateWidget và dispose, mỗi phương thức có mục đích riêng
  • mounted — một cờ an toàn quan trọng ngăn các lệnh gọi setState sau khi xóa widget khỏi cây
  • widget — thuộc tính State để truy cập tham số StatefulWidget được liên kết, được cập nhật thông qua didUpdateWidget
  • Cô lập — State không có quyền truy cập vào các State khác; giao tiếp giữa các widget được thực hiện thông qua InheritedWidget hoặc công cụ bên ngoài
  • setState — không gọi build ngay lập tức, chỉ đánh dấu State là dirty để xây dựng lại trong khung hình tiếp theo
  • Quy tắc — sử dụng State cho dữ liệu cục bộ của widget; chuyển trạng thái toàn cục sang các lớp bên ngoài (Riverpod, Bloc)

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