← Назад к вопросам
Как работает метод setState?
2.0 Middle🔥 231 комментариев
#Flutter виджеты#State Management
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
setState в Flutter: Как работает механизм обновления состояния
setState — одна из самых используемых функций в Flutter, но многие разработчики не понимают, как она работает под капотом. Это не просто "пересчитай виджет" — это сложный механизм, связанный с жизненным циклом и сборкой кадров.
1. Что делает setState?
setState() — это метод State класса, который:
- Вызывает переданный callback (где ты меняешь переменные)
- Помечает виджет как "dirty" (грязный - требует перестроения)
- Запускает пересборку дерева виджетов
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
void increment() {
setState(() {
counter++; // Меняешь состояние
});
// После блока setState() запускается перестроение
}
@override
Widget build(BuildContext context) {
return Text('Counter: $counter');
}
}
2. Жизненный цикл setState
Шаг 1: Вызов setState()
setState(() {
counter++; // Блок выполняется СИНХРОННО
});
// После этой скобки блок уже выполнен
Шаг 2: Пометка как dirty
// Внутри Dart код выглядит примерно так:
void setState(VoidCallback fn) {
fn(); // Выполняем callback
markNeedsBuild(); // Помечаем для пересборки
}
Шаг 3: Планирование пересборки
Вызов setState()
↓
marked as dirty
↓
Ждём следующего VSYNC
↓
Вызывается build()
↓
Обновляется UI
3. Процесс пересборки в деталях
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int count = 0;
@override
void initState() {
super.initState();
print('initState вызвана ОДИН раз'); // При создании
}
@override
Widget build(BuildContext context) {
print('build() вызвана - count=$count'); // При setState
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
onPressed: () {
setState(() {
count++; // Запускает rebuild
});
},
child: Text('Increment'),
),
],
);
}
}
// Консоль:
// initState вызвана ОДИН раз
// build() вызвана - count=0
// (пользователь нажимает кнопку)
// build() вызвана - count=1
// build() вызвана - count=2
// ...
4. Батчинг (Batching) setState
Если вызвать setState несколько раз быстро, Flutter объединит их в один rebuild.
void handleMultipleChanges() {
setState(() {
counter++;
name = 'Updated';
isActive = true;
}); // ОДИН build() вызовется
// ❌ НЕ ДЕЛАЙ ТАК:
// setState(() => counter++);
// setState(() => name = 'Updated');
// setState(() => isActive = true);
// Это может привести к трём build() вызовам
}
5. Batch обновления
class ComplexWidget extends StatefulWidget {
@override
State<ComplexWidget> createState() => _ComplexWidgetState();
}
class _ComplexWidgetState extends State<ComplexWidget> {
String name = 'User';
int age = 0;
String email = '';
bool isSubscribed = false;
void updateUserProfile(String newName, int newAge, String newEmail) {
// ✅ Правильно: всё в одном setState
setState(() {
name = newName;
age = newAge;
email = newEmail;
isSubscribed = true;
});
// Вызовется build() ОДИН раз
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Name: $name'),
Text('Age: $age'),
Text('Email: $email'),
],
);
}
}
6. setState не может быть async
// ❌ ОШИБКА: setState не может быть async
setState(() async {
data = await fetchData(); // ❌ Это не работает так
});
// ✅ Правильно: fetch данные, потом setState
final data = await fetchData();
setState(() {
this.data = data; // Теперь присваиваем
});
// ✅ Или через Future then:
fetchData().then((data) {
setState(() {
this.data = data;
});
});
7. Когда setState вызывается после dispose
Это частая ошибка.
class APIWidget extends StatefulWidget {
@override
State<APIWidget> createState() => _APIWidgetState();
}
class _APIWidgetState extends State<APIWidget> {
late Future<Data> futureData;
@override
void initState() {
super.initState();
futureData = fetchData(); // Запускаем запрос
}
@override
void dispose() {
// Widget удаляется, но запрос ещё в процессе
super.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<Data>(
future: futureData,
builder: (context, snapshot) {
if (snapshot.hasData) {
// ❌ ОПАСНО: widget может быть disposed, но callback ещё вызовется
return DataWidget(snapshot.data!);
}
return CircularProgressIndicator();
},
);
}
}
Правильная обработка:
class SafeAPIWidget extends StatefulWidget {
@override
State<SafeAPIWidget> createState() => _SafeAPIWidgetState();
}
class _SafeAPIWidgetState extends State<SafeAPIWidget> {
late Future<Data> futureData;
@override
void initState() {
super.initState();
futureData = fetchData();
}
@override
void dispose() {
// Отменяем запрос если возможно
super.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<Data>(
future: futureData,
builder: (context, snapshot) {
if (!mounted) return SizedBox(); // ✅ Проверяем mounted
if (snapshot.hasData) {
return DataWidget(snapshot.data!);
}
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return CircularProgressIndicator();
},
);
}
}
8. mounted проверка
class _MyWidgetState extends State<MyWidget> {
void saveData() {
saveToDatabase().then((_) {
// ✅ Проверяем, что widget ещё в дереве
if (mounted) {
setState(() {
isSaved = true;
});
}
});
}
@override
Widget build(BuildContext context) {
return Text('Saved: $isSaved');
}
}
9. setState vs другие способы обновления
| Способ | Когда использовать | Плюсы | Минусы |
|---|---|---|---|
| setState | StatefulWidget | Простой, встроенный | Перестраивает весь виджет |
| Provider | Управление состоянием | Переиспользуемо, эффективно | Нужно настраивать |
| Riverpod | Reactive состояние | Мощный, type-safe | Круче кривая обучения |
| BLoC | Архитектура | Масштабируемо | Много boilerplate |
10. Производительность setState
// ❌ МЕДЛЕННО: перестраивает весь список
class SlowList extends StatefulWidget {
@override
State<SlowList> createState() => _SlowListState();
}
class _SlowListState extends State<SlowList> {
List<Item> items = [];
void addItem(Item item) {
setState(() {
items.add(item); // Перестраивает весь список!
});
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(items[index]),
);
}
}
// ✅ БЫСТРЕЕ: используй Provider или Riverpod
final itemsProvider = StateNotifierProvider((ref) => ItemsNotifier());
class FastList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final items = ref.watch(itemsProvider);
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemTile(items[index]),
);
}
}
11. Правильное использование setState
// ✅ ХОРОШИЕ ПРАКТИКИ:
class FormWidget extends StatefulWidget {
@override
State<FormWidget> createState() => _FormWidgetState();
}
class _FormWidgetState extends State<FormWidget> {
final nameController = TextEditingController();
String? errorMessage;
bool isLoading = false;
@override
void initState() {
super.initState();
nameController.addListener(() {
// Не используй setState в listener
// TextField сам обновляется
});
}
void submitForm() async {
// 1. Покажи индикатор загрузки
setState(() => isLoading = true);
try {
// 2. Выполни операцию
await submitData(nameController.text);
// 3. Обнови состояние
if (mounted) {
setState(() => errorMessage = null);
}
} catch (e) {
// 4. Обработай ошибки
if (mounted) {
setState(() => errorMessage = e.toString());
}
} finally {
// 5. Убери загрузку
if (mounted) {
setState(() => isLoading = false);
}
}
}
@override
void dispose() {
nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(controller: nameController),
if (errorMessage != null) Text(errorMessage!),
ElevatedButton(
onPressed: isLoading ? null : submitForm,
child: isLoading ? CircularProgressIndicator() : Text('Submit'),
),
],
);
}
}
Вывод
setState — простой механизм, но требует понимания:
- Синхронное выполнение — callback вызывается сразу
- Асинхронное обновление UI — build() вызывается позже
- Батчинг — множественные setState объединяются
- mounted проверка — критична для async операций
- Производительность — setState перестраивает весь виджет
Для больших приложений переходи на Provider, Riverpod или BLoC вместо setState везде.