setState и поток перестроения
setState — это сигнал Flutter: «данные изменились, перерисуй меня».
Суть: вы меняете данные внутри
setState, Flutter помечает виджет «грязным», заново вызываетbuild, сравнивает новое дерево со старым и обновляет только изменившиеся пиксели.
Ключ к производительности — понимание, что setState перестраивает не только сам виджет, но и всё его поддерево. Поэтому если вы вызываете setState в корне экрана, перестраивается весь экран, даже неизменная его часть. Решение — опускать состояние как можно ниже, ближе к тому маленькому виджету, который реально меняется. Тогда дорогая работа касается крошечного куска дерева, а остальное Flutter оставляет нетронутым.
Локальное состояние — это самый простой способ заставить интерфейс реагировать. Счётчик, чекбокс, развёрнутая карточка — всё это живёт в State и обновляется через setState. Понимание точного потока «изменение → перестроение → кадр» избавит вас от большинства багов «почему не обновляется» и «почему тормозит».
class LikeButton extends StatefulWidget {
const LikeButton({super.key});
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
bool liked = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(liked ? Icons.favorite : Icons.favorite_border),
color: liked ? Colors.red : Colors.grey,
onPressed: () {
setState(() { // 1. меняем данные
liked = !liked; // 2. внутри setState
}); // 3. Flutter перестроит виджет
},
);
}
}
Как работает поток под капотом
Когда вы вызываете setState, Flutter не перерисовывает экран мгновенно. Он помечает Element этого виджета как «грязный» и планирует перестроение на следующий кадр. В нужный момент он вызывает build, получает новое дерево виджетов и через алгоритм сравнения (diffing) находит, что именно изменилось. Обновляются только отличающиеся render-объекты — поэтому Flutter держит 60 кадров в секунду.
onPressed -> setState(() { liked = !liked; })
|
v
Element помечен "грязным" (dirty)
|
v
запланирован новый кадр
|
v
build() заново
|
v
сравнение нового и старого дерева (diff)
|
v
обновлены ТОЛЬКО изменившиеся пиксели -> кадр
# Модель потока setState -> build -> diff -> кадр
class Widget:
def __init__(self):
self.liked = False
self.dirty = False
def set_state(self, mutator):
mutator()
self.dirty = True # помечаем грязным
self.flush() # планируем перестроение
def build(self):
return 'favorite' if self.liked else 'favorite_border'
def flush(self):
if self.dirty:
new_tree = self.build()
print('кадр обновлён:', new_tree)
self.dirty = False
w = Widget()
w.flush() # ничего, не грязный
w.set_state(lambda: setattr(w, 'liked', True)) # лайк -> перестроение
w.set_state(lambda: setattr(w, 'liked', False)) # снятие лайка
Частые ошибки
- Менять данные вне
setState— экран не обновится. - Тяжёлая работа в
setState— он должен только менять данные; запросы и вычисления выносите наружу. setStateпослеdispose(например, по завершении запроса на удалённом экране) — ошибка; проверяйтеmounted.
Best practices
- Внутри
setStateдержите только присваивания, а не логику и не сетевые вызовы. - Делайте StatefulWidget маленьким — тогда перестраивается лишь он, а не весь экран.
- Перед
setStateпослеawaitпроверяйтеif (mounted).
Помогает и щедрое использование const-виджетов. Помеченный const виджет создаётся один раз и при перестроении родителя переиспользуется без пересоздания — Flutter мгновенно понимает, что он не изменился. Расставляя const везде, где данные не зависят от меняющегося состояния, вы бесплатно ускоряете приложение и снимаете нагрузку с механизма перестроения. Это привычка, которую стоит выработать с первых дней.
Итог: setState — простой и мощный механизм локального состояния. Зная поток «грязный Element → build → diff → кадр», вы понимаете, почему Flutter быстр и как избегать лишних перестроений. Для данных, общих между экранами, нужен другой подход — об этом дальше.