> Какие типы виджетов есть во Flutter (Flutter)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Верме

Стек: Flutter

> Пример ответа

Короткий ответ

Во Flutter виджеты делятся на две основные категории: StatelessWidget и StatefulWidget. Первые - статичные, не хранят состояние и перестраиваются только при изменении входных данных. Вторые - динамические, хранят mutable-состояние через State и перестраиваются при его изменении. Также выделяют InheritedWidget для эффективной передачи данных вниз по дереву, RenderObjectWidget для низкоуровневого рендеринга и ProxyWidget для модификации дерева без собственного рендеринга.

Подробное объяснение

Базовое деление на stateless и stateful - фундаментально, но на практике важно понимать более глубокую иерархию. StatelessWidget - это виджет, который описывает часть интерфейса, зависящую только от конфигурации (параметров конструктора) и контекста. Он не имеет внутреннего изменяемого состояния. StatefulWidget - сам по себе неизменяемый, но создаёт State-объект, который живёт отдельно и хранит состояние. Перестройка происходит через setState.

Помимо этого, есть специализированные типы. InheritedWidget - позволяет эффективно передавать данные от родителя к глубоким потомкам без явной прокидывания через конструкторы. Это основа для Provider, Riverpod и других решений. ProxyWidget - оборачивает child и модифицирует его, например, ParentDataWidget (как Positioned в Stack). RenderObjectWidget - низкоуровневый тип, который создаёт RenderObject для фактической отрисовки; большинство разработчиков работают с ним косвенно через StatelessWidget и StatefulWidget.

Также стоит упомянуть Sliver-виджеты для кастомной прокрутки и StatefulWidget с AutomaticKeepAliveClientMixin для сохранения состояния в списках. На уровне архитектуры выделяют StatefulWidget с ChangeNotifier или ValueNotifier для реактивности.

На практике

В реальной работе выбор между stateless и stateful - это вопрос не только наличия изменяемых данных, но и производительности. Лишний StatefulWidget создаёт дополнительные накладные расходы на управление состоянием. Частая ошибка - делать stateful виджет там, где достаточно stateless с поднятием состояния выше по дереву.

Для передачи данных вниз лучше использовать InheritedWidget или готовые решения на его основе (Provider, Riverpod), а не прокидывать колбэки через десятки уровней. Для сложных анимаций - AnimatedWidget или AnimatedBuilder. Для кастомной прокрутки - SliverList, SliverGrid и их комбинации.

Важно помнить, что StatefulWidget не гарантирует сохранение состояния при перестройке родителя - для этого нужен GlobalKey или AutomaticKeepAliveClientMixin. Также не стоит хранить в State данные, которые можно вычислить из конфигурации виджета.

Пример кода

// StatelessWidget - простой, без состояния
class Greeting extends StatelessWidget {
  final String name;
  const Greeting({super.key, required this.name});

  @override
  Widget build(BuildContext context) {
    return Text('Hello, $name!');
  }
}

// StatefulWidget - с изменяемым состоянием
class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

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

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(onPressed: _increment, child: const Text('+')),
      ],
    );
  }
}

// InheritedWidget - для передачи данных вниз
class ThemeProvider extends InheritedWidget {
  final Color color;
  const ThemeProvider({
    super.key,
    required this.color,
    required super.child,
  });

  static ThemeProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
  }

  @override
  bool updateShouldNotify(ThemeProvider oldWidget) => color != oldWidget.color;
}

Как отвечать на собеседовании

Начните с базовой классификации: stateless и stateful. Затем покажите глубину - упомяните InheritedWidget, ProxyWidget, RenderObjectWidget. Объясните, когда какой тип использовать, и приведите примеры из практики. Если спросят про производительность - расскажите про перестройку виджетов, const-конструкторы и RepaintBoundary. Хорошо показать понимание жизненного цикла State: initState, didUpdateWidget, dispose. Также стоит упомянуть, что StatefulWidget сам по себе immutable, а состояние хранится в State-объекте.

Что проверяет интервьюер

Интервьюер оценивает не знание определений, а понимание архитектуры Flutter. Проверяется: умение выбирать правильный тип виджета под задачу, понимание жизненного цикла, осознание trade-off между производительностью и удобством. Также важно показать знание экосистемы - Provider, Riverpod, Bloc - и то, как они используют InheritedWidget. Senior-уровень подразумевает умение объяснить, почему setState вызывает перестройку только поддерева, а не всего приложения.

Типичные ошибки

  • Путаница между StatelessWidget и StatefulWidget - например, использование stateful для виджета, который не хранит состояние.
  • Хранение в State данных, которые можно вычислить из widget - это приводит к лишним перестройкам.
  • Непонимание, что State переживает перестройку виджета, но не переживает удаление из дерева.
  • Игнорирование const-конструкторов - это снижает производительность.
  • Использование InheritedWidget напрямую без updateShouldNotify - приводит к лишним перестройкам подписчиков.
  • Забывают про AutomaticKeepAliveClientMixin при работе со списками - состояние теряется при прокрутке.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью