setState() — bản chất, cơ chế hoạt động và ứng dụng

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

setState() là phương thức chính của State trong Flutter, thông báo cho framework về thay đổi dữ liệu và kích hoạt xây dựng lại giao diện. Theo tài liệu chính thức của Flutter (Flutter.dev, 2026), setState là cơ chế phản ứng chính trong StatefulWidget: nếu không gọi nó, UI sẽ không biết về thay đổi của các trường State và sẽ giữ nguyên trạng thái trước đó. Phương thức này chấp nhận một VoidCallback, bên trong đó nhà phát triển sửa đổi các trường có thể thay đổi, sau đó Flutter tự động gọi build để xây dựng lại widget.

Những điểm chính

  • setState() — một phương thức State đánh dấu widget là dirty và lên lịch xây dựng lại UI trong frame tiếp theo
  • Callback — setState chấp nhận VoidCallback, bên trong đó tất cả thay đổi trường State ảnh hưởng đến UI phải được thực hiện
  • Bất đồng bộ — setTimeout hoặc Future bên trong setState không đảm bảo đồng bộ; các biến đổi sau await phải ở trong một setState khác
  • Hiệu suất — mỗi lần gọi setState xây dựng lại toàn bộ widget; để giảm thiểu, hãy sử dụng widget con const
  • mounted — trước khi gọi setState trong callback bất đồng bộ, luôn kiểm tra mounted, nếu không — ngoại lệ

setState() là gì?

setState() là một phương thức tích hợp của lớp State trong Flutter, được thiết kế để thông báo cho framework rằng trạng thái nội bộ của widget đã thay đổi và UI cần được xây dựng lại. Nếu không gọi setState, Flutter không biết về các thay đổi — ngay cả khi các trường State đã được sửa đổi, giao diện sẽ không thay đổi cho đến lần xây dựng lại bắt buộc tiếp theo bởi widget cha.

Chữ ký phương thức: void setState(VoidCallback fn). Callback được thực thi đồng bộ bên trong setState, và chỉ sau khi hoàn thành, State mới được đánh dấu là dirty. Điều này đảm bảo rằng tất cả thay đổi được áp dụng nguyên tử trước khi xây dựng lại. Theo Đặc tả Ngôn ngữ Dart (Dart Team, 2026), tính nguyên tử của setState ngăn chặn các điều kiện tranh đua nơi build có thể thấy trạng thái được cập nhật một phần.

setState không nhận đối số, không trả về giá trị và không thể ghi đè. Đây là phương thức final (niêm phong) của lớp State. Nhà phát triển không thể thay đổi hành vi của nó — chỉ sử dụng theo đúng mục đích. Cố gắng gọi setState bên ngoài State (ví dụ, từ một lớp khác) là không thể vì phương thức được khai báo trong lớp State.

setState không thay đổi trạng thái — bạn mới là người thay đổi

Một quan niệm sai phổ biến là cho rằng setState tự thay đổi trạng thái. Điều này không đúng. setState chỉ gọi callback được truyền vào (trong đó nhà phát triển sửa đổi các trường) và sau đó báo hiệu cho framework về nhu cầu build. Callback là bắt buộc — truyền null hoặc callback rỗng sẽ gây ra lỗi.

setState() hoạt động như thế nào?

Cơ chế hoạt động của setState() có thể được chia thành bốn giai đoạn. Đầu tiên — gọi phương thức với một callback. Thứ hai — thực thi đồng bộ callback, bên trong đó các trường State được sửa đổi. Thứ ba — State được đánh dấu là dirty trong một trường đặc biệt _dirty. Thứ tư — vào cuối microtask hiện tại, Flutter duyệt qua tất cả các phần tử dirty và gọi build của chúng theo thứ tự xuất hiện trong cây.

Một chi tiết quan trọng: setState không gọi build ngay lập tức. Flutter sử dụng chiến lược cập nhật theo lô: tất cả các phần tử dirty được thu thập và xây dựng lại trong một frame duy nhất. Điều này có nghĩa là nếu setState được gọi nhiều lần trong một khối đồng bộ, build sẽ chỉ thực thi một lần — sau khi tất cả thay đổi hoàn tất. Sự tối ưu hóa này ngăn chặn nhiều lần xây dựng lại mỗi frame.

Theo Nhóm Flutter Engine (Google, 2025), cơ chế cờ dirty dựa trên lượt BuildOwner._dirtyElements. Mỗi StatefulElement dirty được thêm vào danh sách và xử lý ở giai đoạn cập nhật frame. Nếu một widget bị xóa khỏi cây trước khi xử lý, nó tự động bị loại khỏi danh sách các phần tử dirty.

Các đảm bảo của setState

  • Callback được thực thi đồng bộ trước khi đánh dấu dirty
  • build được gọi không quá một lần mỗi frame (ngay cả với nhiều lần gọi setState)
  • Cập nhật UI xảy ra trên frame tiếp theo (thường ~16ms ở 60 FPS)
  • Sau dispose, gọi setState bị cấm — ném ngoại lệ
  • Trong build, gọi setState bị cấm — vòng lặp vô hạn

Ví dụ mã Dart

Ví dụ cơ bản về setState() với tăng bộ đếm. Minh họa cách sử dụng đúng: sửa đổi trường bên trong callback:

dart
class _CounterState extends State<CounterWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++; // sửa đổi trường bên trong callback
    });
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _increment,
      child: Text('$_count'),
    );
  }
}

Ví dụ với trường văn bản và bộ điều khiển — setState() để quản lý hiển thị mật khẩu:

dart
class _PasswordFieldState extends State<PasswordField> {
  bool _obscured = true;
  final _controller = TextEditingController();

  void _toggleVisibility() {
    setState(() {
      _obscured = !_obscured;
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      obscureText: _obscured,
      decoration: InputDecoration(
        suffixIcon: IconButton(
          icon: Icon(_obscured ? Icons.visibility : Icons.visibility_off),
          onPressed: _toggleVisibility,
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Trong ví dụ này, setState() chỉ thay đổi trường boolean _obscured, kích hoạt xây dựng lại TextField với biểu tượng mới và chế độ hiển thị. Bộ điều khiển văn bản không được tạo lại — nó được khởi tạo một lần trong initState và giải phóng trong dispose.

Nhiều biến đổi trong một setState

Nếu bạn cần thay đổi nhiều trường, tất cả thay đổi nên được thực hiện trong một setState. Điều này đảm bảo rằng build sẽ thấy trạng thái nhất quán:

dart
setState(() {
  _isLoading = false;
  _items = newItems;
  _error = null;
});

Ba trường được thay đổi trong một callback — build sẽ thực thi một lần và thấy tất cả thay đổi đồng thời. Nếu mỗi lần gọi là một setState riêng biệt, build vẫn sẽ chỉ thực thi một lần nhờ xử lý theo lô các phần tử dirty.

Bất đồng bộ và setState

Một trong những sắc thái quan trọng nhất của setState() là hành vi của nó với các thao tác bất đồng bộ. Callback của setState thực thi đồng bộ, nhưng nếu await được gọi bên trong nó, mã sau await sẽ thực thi sau khi setState đã hoàn thành công việc. Điều này có nghĩa là các thay đổi trường sau await sẽ không được setState hiện tại ghi nhận.

Cách tiếp cận đúng: thao tác bất đồng bộ được thực hiện bên ngoài setState, và setState được gọi sau khi nó hoàn thành. Tất cả mã giữa việc nhận kết quả và gọi setState chạy trong ngữ cảnh đồng bộ sau await:

dart
// ĐÚNG: await bên ngoài setState
Future<void> _loadData() async {
  final result = await ApiService.fetchData();
  setState(() {
    _data = result;
    _isLoading = false;
  });
}

// SAI: await bên trong setState — không đảm bảo cập nhật
void _loadDataWrong() {
  setState(() async {
    _data = await ApiService.fetchData(); // setState trả về trước khi await hoàn thành
    _isLoading = false; // mã này không được setState ghi nhận
  });
}

Theo tài liệu Flutter (Dart async patterns, 2026), truyền callback bất đồng bộ cho setState là một phản mẫu vì setState mong đợi VoidCallback (hàm đồng bộ), trong khi hàm bất đồng bộ trả về Future bị bỏ qua. Các thay đổi sau await đầu tiên trong callback như vậy sẽ không được framework xử lý đúng cách.

Kiểm tra mounted trong kịch bản bất đồng bộ

Trước khi gọi setState() sau một thao tác bất đồng bộ, luôn kiểm tra mounted:

dart
if (mounted) {
  setState(() => _data = data);
}

Nếu widget bị xóa khỏi cây trong thao tác bất đồng bộ, mounted sẽ trở thành false và setState sẽ không được gọi. Điều này ngăn chặn ngoại lệ và rò rỉ tài nguyên.

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

setState() là một cơ chế tiện lợi nhưng có khả năng tốn kém nếu sử dụng thiếu suy nghĩ. Mỗi lần gọi setState xây dựng lại toàn bộ widget và tất cả các hậu duệ của nó (nếu chúng không phải là const). Trong cây sâu hoặc với các lần gọi thường xuyên, điều này có thể dẫn đến giảm FPS.

Các chiến lược tối ưu hóa chính: giảm thiểu khu vực xây dựng lại (trích xuất các phần UI có thể thay đổi vào các StatefulWidget riêng biệt), sử dụng const cho các widget con bất biến, và tránh gọi setState trong widget cha nếu chỉ một chi tiết UX nhỏ thay đổi. Nếu trạng thái được cập nhật với tần suất cao (hoạt ảnh, luồng dữ liệu), hãy cân nhắc AnimatedBuilder hoặc ValueListenableBuilder.

Theo Thực hành Tốt nhất về Hiệu suất Flutter (Flutter.dev, tháng 2 năm 2026), việc phân tích ứng dụng thực tế cho thấy có thể thay thế tới 40% tất cả các lần gọi setState bằng widget con const hoặc bộ xây dựng phản ứng (StreamBuilder, FutureBuilder). Điều này giảm thời gian xây dựng frame trung bình từ 15–25%.

Khi nào setState là dư thừa

Kịch bảnGiải pháp thay thếLợi ích
Hoạt ảnhAnimatedBuilderChỉ xây dựng lại widget hoạt ảnh
Luồng dữ liệuStreamBuilderPhản ứng với từng phần tử luồng
Kết quả tương laiFutureBuilderQuản lý trạng thái tải/lỗi
Giá trị cục bộValueListenableBuilderPhản ứng với thay đổi giá trị đơn

Các lựa chọn thay thế setState

Mặc dù tính linh hoạt của setState(), trong các dự án lớn, nó chủ yếu được sử dụng cho trạng thái cục bộ. Đối với trạng thái toàn cầu hoặc chia sẻ, các giải pháp chuyên biệt được sử dụng, mỗi giải pháp thay thế hoặc bao bọc setState.

Provider sử dụng ChangeNotifier + notifyListeners như một dạng tương tự của setState, nhưng với khả năng đăng ký nhiều widget. Bloc sử dụng Streams — trạng thái được thay đổi bằng cách thêm sự kiện vào StreamController. Riverpod kết hợp các cách tiếp cận, cung cấp cả quản lý cục bộ (StateProvider) và bất đồng bộ (AsyncNotifier) mà không ràng buộc với StatefulWidget. Cả ba cách tiếp cận đều loại bỏ nhu cầu gọi setState thủ công — cập nhật UI tự động xảy ra khi dữ liệu thay đổi.

Theo Khảo sát Cộng đồng Flutter 2025 (Flutter Foundation, tháng 12 năm 2025), 74% nhà phát triển sử dụng ít nhất một công cụ quản lý trạng thái ngoài setState. Đồng thời, 92% tiếp tục sử dụng setState cho dữ liệu cục bộ của trường văn bản, hộp kiểm hoặc bộ đếm đơn giản — điều này được coi là thực hành tốt nhất.

Khi nào nên giữ setState

  • Trạng thái chỉ được sử dụng bởi một widget
  • Giá trị boolean hoặc số đơn giản (tiêu điểm, hiển thị, bộ đếm)
  • Tạo mẫu nhanh và thử nghiệm
  • Bộ điều khiển (TextEditingController, PageController) vẫn yêu cầu StatefulWidget

Lỗi phổ biến

Lỗi đầu tiên và nguy hiểm nhất là gọi setState sau dispose. Một thao tác bất đồng bộ bắt đầu trong initState, người dùng rời khỏi màn hình, widget bị xóa và callback bất đồng bộ gọi setState — ứng dụng sập với ngoại lệ. Giải pháp — luôn kiểm tra mounted trước khi gọi.

Lỗi thứ hai là gọi setState bên trong build. Điều này dẫn đến vòng lặp vô hạn: build → setState → dirty → build → setState → ... Flutter không chặn lời gọi như vậy (bạn sẽ nhận được StackOverflowError). setState chỉ có thể được gọi để phản hồi một sự kiện (nhấn nút, hoàn thành Future, dữ liệu từ luồng).

Lỗi thứ ba là sửa đổi trường State mà không gọi setState. Nhà phát triển viết _count++ và mong đợi UI cập nhật. Flutter không thể tự động theo dõi thay đổi trường — nó cần một tín hiệu rõ ràng qua setState. Đây là sự khác biệt cơ bản so với các framework phản ứng như Vue.js, nơi thay đổi dữ liệu tự động kích hoạt cập nhật.

Lỗi thứ tư là gọi setState với callback bất đồng bộ (async lambda). Như được mô tả trong phần bất đồng bộ, các thay đổi sau await sẽ không được ghi nhận, dẫn đến lỗi khó tái tạo. Sử dụng callback đồng bộ và gọi setState sau await.

Danh sách kiểm tra setState an toàn

  • Luôn kiểm tra mounted trong callback bất đồng bộ
  • Không gọi setState bên trong build
  • Không truyền lambda async cho setState
  • Không sửa đổi trường State bên ngoài setState
  • Nếu thay đổi nhiều trường — hãy làm trong một setState

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

setState() làm gì trong Flutter?

setState() thông báo cho Flutter rằng dữ liệu nội bộ của StatefulWidget đã thay đổi và UI cần được xây dựng lại. Phương thức này chấp nhận một callback, thực thi nó đồng bộ, đánh dấu widget là dirty và lên lịch gọi build trong frame tiếp theo.

Điều gì xảy ra nếu tôi không gọi setState sau khi thay đổi trường?

UI sẽ không cập nhật. Flutter không tự động theo dõi thay đổi trường. Giá trị trường thay đổi trong bộ nhớ, nhưng widget vẫn giữ nguyên trạng thái trước đó cho đến lần xây dựng lại bắt buộc tiếp theo bởi widget cha.

Có thể gọi setState bên trong build không?

Không. Điều này dẫn đến vòng lặp vô hạn: build gọi setState, setState đánh dấu widget là dirty và lại gọi build. Flutter không chặn tình huống này — ứng dụng sẽ sập với StackOverflowError.

Sẽ có bao nhiêu lần build thực thi với hai lần gọi setState liên tiếp?

Build sẽ thực thi một lần. Flutter thu thập tất cả các phần tử dirty và xây dựng lại chúng theo lô vào cuối frame. Lần gọi setState thứ hai trước khi xử lý chỉ đơn giản thêm phần tử vào cùng danh sách dirty — không có xây dựng lại lặp lại.

mounted là gì và tại sao nó quan trọng cho setState?

mounted là một cờ boolean cho biết widget vẫn còn trong cây. Nếu setState được gọi sau thao tác bất đồng bộ mà không kiểm tra mounted và widget đã bị xóa — ứng dụng sập với ngoại lệ "setState called after dispose".

Tổng kết

  • setState() — một phương thức State thông báo cho Flutter về thay đổi dữ liệu và kích hoạt xây dựng lại UI trong frame tiếp theo
  • Cơ chế hoạt động — thực thi callback đồng bộ, đánh dấu State là dirty, xây dựng lại theo lô tất cả phần tử dirty vào cuối frame
  • Bất đồng bộ — callback async trong setState không hoạt động; await phải ở bên ngoài và setState sau khi nhận kết quả
  • mounted — kiểm tra bắt buộc trước setState trong thao tác bất đồng bộ để ngăn ngoại lệ
  • Tối ưu hóa — giảm thiểu khu vực xây dựng lại qua widget con const và di chuyển hoạt ảnh sang AnimatedBuilder
  • Giải pháp thay thế — cho trạng thái toàn cầu sử dụng Riverpod, Bloc hoặc Provider; giữ setState cho dữ liệu cục bộ
  • Quy tắc — không gọi setState trong build, không truyền async lambda, luôn kiểm tra mounted

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