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, к которому мы переходим.