StatelessWidget и метод build

StatelessWidget рисует интерфейс, который не меняется сам по себе, — его внешний вид целиком определяют входные данные.

Суть: StatelessWidget — неизменяемый виджет с единственным методом build, который описывает интерфейс. Он перестраивается только когда родитель передаёт новые данные.

Важно понять, почему поля StatelessWidget обязаны быть неизменяемыми. Flutter в любой момент может выбросить старый экземпляр виджета и создать новый с теми же параметрами — для него виджеты дёшевы и одноразовы. Если бы поля можно было менять, эти изменения терялись бы при каждой перерисовке. Поэтому всё, что должно меняться и сохраняться между перестроениями, выносится в объект State, а StatelessWidget остаётся чистым описанием по фиксированным входным данным.

Большинство виджетов в приложении ничего не «помнят» и не меняются изнутри: заголовок экрана, карточка товара, аватарка. Им просто дают данные, и они их показывают. Для таких случаев существует StatelessWidget — самый частый тип виджета. Вы создаёте класс, наследуете от StatelessWidget и реализуете метод build.

class Greeting extends StatelessWidget {
  final String name;
  const Greeting({super.key, required this.name});

  @override
  Widget build(BuildContext context) {
    return Text('Привет, $name!');
  }
}

// использование:
const Greeting(name: 'Аня');

Поля виджета (name) — это его входные данные, всегда final. Метод build возвращает дерево виджетов, которое нужно показать. Он принимает BuildContext — «адрес» виджета в дереве, через который можно достучаться до темы, размеров экрана и родителей.

Как работает build под капотом

Flutter вызывает build, когда нужно нарисовать виджет или когда что-то изменилось выше по дереву. Метод должен быть чистым: он только описывает интерфейс по текущим данным и не делает побочных эффектов — не ходит в сеть, не меняет переменные. Flutter может вызвать build много раз в секунду, поэтому он обязан быть быстрым и предсказуемым.

  Родитель передал name='Аня'
            |
            v
   +------------------+
   |  build(context)  |  <- чистая функция: данные -> дерево
   +------------------+
            |
            v
     Text('Привет, Аня!')

  Изменился name?  -> родитель пересоздаёт Greeting
                   -> Flutter снова зовёт build
                   -> новое дерево -> новый кадр

BuildContext стоит понимать как ссылку на место виджета в дереве. Через него вызывают Theme.of(context) (текущая тема) или MediaQuery.of(context) (размеры экрана). Контекст у каждого виджета свой и знает только то, что выше него.

# Модель StatelessWidget: build как чистая функция данных в "дерево"
def build_greeting(name):
    # чистая функция: одинаковый вход -> одинаковый выход, без side effects
    return f"Text('Привет, {name}!')"

print(build_greeting('Аня'))    # перерисовка при name='Аня'
print(build_greeting('Боря'))   # родитель дал новые данные -> новый build

# build НЕ должен делать так (побочный эффект):
# counter += 1   # запрещено внутри build

Частые ошибки

  • Менять переменные внутри build. Это побочный эффект; build должен только возвращать дерево.
  • Делать сетевые запросы в build. Он вызывается часто — запрос полетит десятки раз. Для этого есть initState (следующий раздел).
  • Делать поля не-final. StatelessWidget неизменяем; меняющиеся данные — это уже state.

Best practices

  • По умолчанию используйте StatelessWidget; переходите на StatefulWidget только когда виджету нужна внутренняя память.
  • Добавляйте const-конструктор и super.key — это включает оптимизации Flutter.
  • Держите build коротким и чистым; тяжёлую логику выносите в отдельные методы или классы.

Хорошая привычка — дробить интерфейс на множество маленьких StatelessWidget вместо одного гигантского метода build на двести строк. Маленькие виджеты легче читать, переиспользовать и тестировать, а Flutter перестраивает только те из них, чьи данные изменились. Когда вы видите, что build разросся, это сигнал выделить часть дерева в отдельный именованный виджет — код сразу станет понятнее.

Итог: StatelessWidget — рабочая лошадка интерфейса: чистый build, неизменяемые поля, перерисовка только по воле родителя. Когда виджету нужно помнить и менять что-то самому — приходит StatefulWidget, к которому мы переходим.

Проверьте себя
1. Каким должен быть метод build у StatelessWidget?
AМенять глобальные переменные
BЧистым: только описывать интерфейс по данным, без побочных эффектов
CДелать сетевые запросы
DВозвращать null
2. Для чего нужен BuildContext в методе build?
AХранит состояние виджета
BЭто адрес виджета в дереве: доступ к теме, размерам экрана, родителям
CДелает виджет изменяемым
DЗапускает приложение