Перейти к основному содержимому

Виджеты и композиция

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

Почему это важно. Почти все проблемы вида «Flutter тормозит» и «состояние куда-то пропало» растут из модели, в которой виджет — долгоживущий объект. Это не так, и поведение фреймворка становится осмысленным только после того, как это укладывается в голове.

Что нужно понимать

  • Зачем нужен build: описывать, а не делать
  • Что переживает перестроение, а что нет
  • Почему фреймворк считает два виджета одним и тем же — или разными
  • Когда кусок интерфейса пора выносить в отдельный виджет
  • Что такое const и на чём он экономит

Основные темы

Деревья

  • Widget, element и render object — три дерева и задача каждого
  • Перестроение и почему оно дёшево по замыслу
  • Ключи: когда важна идентичность и во что обходятся ValueKey и GlobalKey
  • BuildContext как позиция в дереве, а не мешок с данными

Stateless и stateful

  • Где живёт состояние и как поднимать его выше
  • initState, didUpdateWidget, dispose — и что относится к каждому из них
  • Почему в build не должно быть побочных эффектов
  • StatefulWidget против отдельного решения для управления состоянием

Композиция

  • Мелкие виджеты вместо длинных методов build
  • Метод, возвращающий виджет, против настоящего класса-виджета — и почему разница существенна
  • Билдеры, слоты и передача children вместо конфигурации
  • InheritedWidget и чтение данных сверху

Перестроения

  • Что запускает перестроение и как далеко оно расходится
  • const-конструкторы и перестроения, которых они позволяют избежать
  • Перестроение поддерева вместо целого экрана
  • Как распознать дорогой build

Уровни

УровеньКак это выглядит
JuniorСобирает экраны из готовых виджетов. Корректно пользуется setState.
MiddleРазбивает интерфейс на мелкие виджеты, понимает дерево элементов достаточно, чтобы объяснить ключи, и контролирует область перестроения.
SeniorПроектирует переиспользуемые API виджетов, осознанно применяет ключи и жизненный цикл, диагностирует проблемы перестроения и компоновки по дереву.

Практика

Для начала

  • Разберите метод build на части Возьмите build длиннее сотни строк и разложите его на именованные виджеты.

  • Расставьте const везде, где можно Включите линт, устраните предупреждения и посмотрите на число перестроений.

  • Проследите за перестроением Счётчики перестроений в инспекторе покажут, как далеко достаёт один setState.

Глубже

  • Там, где нужен ключ Соберите список stateful-элементов с изменением порядка и найдите баг, который лечится только ключом.

  • Спроектируйте API виджета Напишите переиспользуемый виджет, которым будут пользоваться другие, и сделайте неправильное применение невозможным.

  • Сузьте слишком широкое перестроение Найдите setState, который перестраивает пол-экрана, и ограничьте его.

Проверьте себя

  • Что именно происходит между setState и изменением пикселей на экране?
  • Почему состояние иногда прилипает не к тому элементу списка?
  • Когда вспомогательный метод, возвращающий виджет, — неверный выбор?
  • Что в вашем самом длинном build там быть не должно?
  • Как узнать, что именно перестраивается и как часто?
  • Когда GlobalKey обходится дороже, чем даёт?

Материалы

  • Introduction to widgets — официальная точка входа: композиция и разделение на stateful и stateless.
  • Flutter architectural overview — три дерева и путь от фреймворка до GPU. Самая полезная страница из всех: после неё остальное перестаёт казаться произвольным.
  • Inside Flutter — почему перестроения дёшевы, написано теми, кто их такими сделал. Разбирает алгоритмы, стоящие за наблюдаемым поведением.
  • When to use keys — объяснение от команды Flutter; именно после него у большинства ключи встают на место.
  • Flutter Widget of the Week — по одному виджету за минуту, от команды. Самый дешёвый способ узнать, что уже есть, прежде чем писать своё.