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

Чем отличается Stateful widget от Stateless widget?

1.2 Junior🔥 282 комментариев
#Flutter виджеты#State Management

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

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

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

Stateless Widget vs Stateful Widget: Когда что использовать

Это один из самых фундаментальных вопросов во Flutter. Понимание разницы между stateless и stateful виджетами критично для написания правильного и эффективного кода.

Основная разница

Stateless Widget:

  • Не имеет внутреннего состояния (state)
  • Неизменяемый (immutable)
  • Строит UI один раз и никогда не меняется
  • Быстрый и лёгкий

Stateful Widget:

  • Имеет изменяемое состояние (State)
  • Может перестраиваться (rebuild) при изменении состояния
  • Может иметь lifecycle (initState, dispose)
  • Более сложный

Stateless Widget: Простой и чистый

Пример:

class WelcomeScreen extends StatelessWidget {
  final String userName;
  
  const WelcomeScreen({required this.userName});
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Welcome')),
      body: Center(
        child: Text('Hello, $userName!'),
      ),
    );
  }
}

// Использование
WelcomeScreen(userName: 'Alice')

Когда использовать Stateless:

  • Данные приходят через конструктор (props)
  • UI не меняется внутри компонента
  • Презентационные компоненты (кнопки, карточки, иконки)
  • Компоненты, зависящие от parent

Stateful Widget: Управление состоянием

Структура:

class CounterApp extends StatefulWidget {
  @override
  State<CounterApp> createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int counter = 0;
  
  @override
  void initState() {
    super.initState();
    // Инициализация
    print('Виджет создан');
  }
  
  @override
  void dispose() {
    super.dispose();
    // Очистка ресурсов
    print('Виджет удалён');
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text('Counter: $counter'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            counter++; // Перестройка UI
          });
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Lifecycle методы:

class _MyStateState extends State<MyState> {
  @override
  void initState() {
    super.initState();
    // Вызывается один раз при создании
    // Инициализация переменных, подписка на потоки
  }
  
  @override
  void didUpdateWidget(MyState oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Вызывается когда parent перестроился
    // Обновить state если нужно
  }
  
  @override
  void deactivate() {
    super.deactivate();
    // Вызывается перед dispose
  }
  
  @override
  void dispose() {
    super.dispose();
    // Вызывается один раз перед удалением
    // ОБЯЗАТЕЛЬНО закрыть потоки, отписаться
  }
  
  @override
  Widget build(BuildContext context) {
    // Вызывается при каждом setState()
    return Container();
  }
}

Когда использовать Stateful

Типичные сценарии:

// 1. Управление пользовательским вводом
class LoginForm extends StatefulWidget {
  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  late TextEditingController emailController;
  late TextEditingController passwordController;
  
  @override
  void initState() {
    super.initState();
    emailController = TextEditingController();
    passwordController = TextEditingController();
  }
  
  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(controller: emailController),
        TextField(controller: passwordController),
        ElevatedButton(
          onPressed: login,
          child: Text('Login'),
        ),
      ],
    );
  }
  
  void login() {
    print('Email: ${emailController.text}');
  }
}
// 2. Взаимодействие с внешними ресурсами
class UserProfile extends StatefulWidget {
  final int userId;
  
  const UserProfile({required this.userId});
  
  @override
  State<UserProfile> createState() => _UserProfileState();
}

class _UserProfileState extends State<UserProfile> {
  late StreamSubscription userSubscription;
  User? user;
  
  @override
  void initState() {
    super.initState();
    // Подписаться на обновления
    userSubscription = userService.getUser(widget.userId).listen((newUser) {
      setState(() => user = newUser);
    });
  }
  
  @override
  void dispose() {
    userSubscription.cancel(); // ВАЖНО!
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return user == null
        ? LoadingWidget()
        : UserCard(user: user!);
  }
}
// 3. Анимации
class FadingWidget extends StatefulWidget {
  @override
  State<FadingWidget> createState() => _FadingWidgetState();
}

class _FadingWidgetState extends State<FadingWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> opacity;
  
  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    opacity = Tween(begin: 0.0, end: 1.0).animate(controller);
    controller.forward();
  }
  
  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return FadeTransition(
      opacity: opacity,
      child: Text('Fading text'),
    );
  }
}

Сравнительная таблица

Аспект           Stateless              Stateful
────────────────────────────────────────────────────
Сложность        Простой                Сложнее
Производ.        Быстрее                Медленнее
Изменяемость     Immutable              Mutable
Пересройка       Нет (данные из props)  Да (setState)
Лифцикл          Нет                    Да (init/dispose)
Используемость   ~70% виджетов          ~30% виджетов
Ошибки           Меньше                 Больше
────────────────────────────────────────────────────

Современный подход: Избегайте Stateful

Современный Flutter рекомендует:

  1. Использовать StateManagement (BLoC, Provider, Riverpod)
  2. Минимизировать local state в Stateful
  3. Предпочитать Stateless + Provider/BLoC

Пример с Provider (лучше):

// Stateless + управление через Provider
class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Consumer<CounterProvider>(
          builder: (context, counter, _) {
            return Column(
              children: [
                Text('Count: ${counter.value}'),
                ElevatedButton(
                  onPressed: counter.increment,
                  child: Text('Increment'),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

class CounterProvider extends ChangeNotifier {
  int _value = 0;
  int get value => _value;
  
  void increment() {
    _value++;
    notifyListeners();
  }
}

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

Ошибка 1: Использование Stateful для простого UI

// Плохо
class SimpleButton extends StatefulWidget {
  final String label;
  final VoidCallback onPressed;
  
  @override
  State<SimpleButton> createState() => _SimpleButtonState();
}

// Хорошо
class SimpleButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;
  
  const SimpleButton({required this.label, required this.onPressed});
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(onPressed: onPressed, child: Text(label));
  }
}

Ошибка 2: Забыть dispose

// Плохо — утечка памяти
class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late StreamSubscription subscription;
  
  @override
  void initState() {
    super.initState();
    subscription = stream.listen((_) {});
    // НЕ закрыто!
  }
}

// Хорошо
@override
void dispose() {
  subscription.cancel(); // Очистка!
  super.dispose();
}

Ошибка 3: Использовать Stateful когда нужен Provider

// Плохо
class UserProfile extends StatefulWidget { /* много кода */ }

// Хорошо — используй BLoC/Provider
class UserProfile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<UserBloc, UserState>(
      builder: (context, state) => buildUI(state),
    );
  }
}

Правило большого пальца

Используй Stateless по умолчанию.

Перевдитесь на Stateful только если:

  1. Виджет имеет внутреннее состояние (counter, form fields)
  2. Нужны lifecycle методы (initState, dispose)
  3. Используете TextEditingController или AnimationController
  4. Нельзя использовать StateManagement

Для большей части UI используйте Stateless + StateManagement (Provider/BLoC).

Этот подход делает код чище, тестируемее и проще поддерживать.