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

Что такое дерево виджетов?

1.0 Junior🔥 301 комментариев
#Flutter виджеты

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

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

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

Дерево виджетов в Flutter

Widget Tree (дерево виджетов) — это иерархическая структура, в которой все UI элементы приложения организованы в виде дерева. Каждый виджет может иметь дочерние виджеты, создавая древовидную структуру от корня (MyApp) к листьям (Text, Image).

Структура дерева

MyApp
├── MaterialApp
│   ├── home: HomePage
│   │   ├── Scaffold
│   │   │   ├── appBar: AppBar
│   │   │   │   └── title: Text
│   │   │   ├── body: Column
│   │   │   │   ├── Row
│   │   │   │   │   ├── Icon
│   │   │   │   │   └── Text
│   │   │   │   └── ListView
│   │   │   │       └── ListTile (repeated)
│   │   │   └── floatingActionButton: FloatingActionButton
│   │   │       └── Icon

Элементы дерева

1. Widget — объявление UI (неизменяемо):

// Widget описывает КАК должен выглядеть UI
text = Text('Hello', style: TextStyle(fontSize: 18));

2. Element — экземпляр Widget в дереве:

// Element — это живой объект в дереве,
// которому живёт в памяти во время жизни приложения

3. RenderObject — отвечает за рисование:

// RenderObject содержит логику layout, paint и events

Трёхслойная архитектура

Widget Tree (неизменяемо)
     ↓
Element Tree (управляет жизненным циклом)
     ↓
Render Tree (отрисовка и layout)
// Пример преобразования
Widget:  Text('Hello')  ← Это просто описание
         ↓
Element: TextElement   ← Это экземпляр в дереве
         ↓
Render:  RenderParagraph ← Это что отрисовывается

Жизненный цикл элемента в дереве

1. mount()       ← Element добавлен в дерево
2. build()       ← Widget создаёт UI
3. update()      ← Widget обновился
4. reassemble()  ← Hot reload
5. deactivate()  ← Удаление из дерева
6. unmount()     ← Полное удаление из памяти

Как происходит построение дерева

Начальное построение:

void main() {
  runApp(MyApp()); // Добавляет MyApp в дерево
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // build() вызывается для создания дочерних виджетов
    return MaterialApp(
      home: HomePage(),
    );
  }
}

Обновление дерева с setState:

class _CounterState extends State<Counter> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Text('Count: $count'),
      ElevatedButton(
        onPressed: () {
          setState(() => count++); // Триггер перестройки
        },
        child: Text('Increment'),
      ),
    ]);
  }
}

// Что происходит при setState():
// 1. State отмечается как "грязное"
// 2. build() вызывается снова
// 3. Создаётся новый Widget Tree
// 4. Flutter сравнивает старое и новое дерево (diffing)
// 5. Обновляет только изменённые элементы

Reconciliation (сравнение деревьев)

Flutter использует эффективный алгоритм для обновления:

// Старое дерево
Column(
  children: [Text('Count: 0')],
)

// Новое дерево
Column(
  children: [Text('Count: 1')],
)

// Flutter видит:
// 1. Column остался → переиспользовать Element
// 2. Text изменился → обновить текст
// 3. Остальное не трогать

// Это намного эффективнее, чем пересоздавать всё

Ключи (Keys) для контроля дерева

Для сложных списков используются ключи:

// Без ключей: если переставить элементы, state может
// прилипнуть к неправильному элементу
Column(children: [
  StatefulWidget1(),
  StatefulWidget2(),
]);

// С ключами: Flutter знает какой Element куда идёт
Column(children: [
  KeyedWidget(key: ValueKey(1), child: StatefulWidget1()),
  KeyedWidget(key: ValueKey(2), child: StatefulWidget2()),
]);

// Для списков используйте:
ListView.builder(
  itemBuilder: (context, index) => ListItem(
    key: ValueKey(items[index].id), // ← Обязательно!
    item: items[index],
  ),
)

InheritedWidget: передача данных по дереву

class MyTheme extends InheritedWidget {
  final Color primaryColor;

  const MyTheme({
    required this.primaryColor,
    required super.child,
  });

  static MyTheme of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyTheme>()!;
  }

  @override
  bool updateShouldNotify(MyTheme oldWidget) {
    return primaryColor != oldWidget.primaryColor;
  }
}

// Использование:
final theme = MyTheme.of(context);
print(theme.primaryColor);

BuildContext: навигация по дереву

// BuildContext содержит информацию о позиции элемента в дереве
var parentScaffold = Scaffold.of(context); // Родитель Scaffold
var theme = Theme.of(context); // Ближайшая тема
var navigator = Navigator.of(context); // Navigator

// Используется для поиска элементов вверх по дереву

DevTools: инспекция дерева

flutter pub global run devtools
# Открывает DevTools со вкладкой Widget Inspector

Там можно:

  • Видеть всё дерево
  • Инспектировать свойства виджетов
  • Поиск виджетов
  • Анализ производительности

Производительность дерева

// Плохо: весь Column перестраивается
StatefulWidget buildCounter() {
  return Column(
    children: [
      Counter(), // Перестраивается при setState
      ExpensiveWidget(), // Перестраивается даже если не меняется!
      AnotherExpensive(),
    ],
  );
}

// Хорошо: изолировать setState в отдельный виджет
class Counter extends StatefulWidget { ... }

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Counter(), // Только Counter перестраивается
        ExpensiveWidget(), // Не перестраивается
        AnotherExpensive(),
      ],
    );
  }
}

Практические советы

  • Разделяйте виджеты — чем меньше части, тем эффективнее обновления
  • Используйте const — const виджеты не перестраиваются
  • Используйте ключи — для списков с динамическим контентом
  • Профилируйте — DevTools покажет какие виджеты часто перестраиваются
  • Знайте BuildContext — он привязан к позиции в дереве

Понимание дерева виджетов — это ключ к написанию эффективного и масштабируемого Flutter кода.

Что такое дерево виджетов? | PrepBro