← Назад к вопросам

Как работает метод setState?

2.0 Middle🔥 231 комментариев
#Flutter виджеты#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

setState в Flutter: Как работает механизм обновления состояния

setState — одна из самых используемых функций в Flutter, но многие разработчики не понимают, как она работает под капотом. Это не просто "пересчитай виджет" — это сложный механизм, связанный с жизненным циклом и сборкой кадров.

1. Что делает setState?

setState() — это метод State класса, который:

  1. Вызывает переданный callback (где ты меняешь переменные)
  2. Помечает виджет как "dirty" (грязный - требует перестроения)
  3. Запускает пересборку дерева виджетов
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 другие способы обновления

СпособКогда использоватьПлюсыМинусы
setStateStatefulWidgetПростой, встроенныйПерестраивает весь виджет
ProviderУправление состояниемПереиспользуемо, эффективноНужно настраивать
RiverpodReactive состояниеМощный, 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 — простой механизм, но требует понимания:

  1. Синхронное выполнение — callback вызывается сразу
  2. Асинхронное обновление UI — build() вызывается позже
  3. Батчинг — множественные setState объединяются
  4. mounted проверка — критична для async операций
  5. Производительность — setState перестраивает весь виджет

Для больших приложений переходи на Provider, Riverpod или BLoC вместо setState везде.

Как работает метод setState? | PrepBro