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

Что такое didUpdateWidget?

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

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

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

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

didUpdateWidget: Отслеживание изменений свойств

didUpdateWidget — это важный lifecycle метод в StatefulWidget, который вызывается когда родительский виджет перестраивается и передаёт новые параметры (properties) в StatefulWidget. Это ключевой метод для реагирования на изменения входных данных.

Когда вызывается didUpdateWidget

class MyStatefulWidget extends StatefulWidget {
  final String title;  // Входное свойство
  
  const MyStatefulWidget({required this.title});
  
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  void initState() {
    super.initState();
    print('initState вызван');
    // Вызывается один раз при создании State
  }
  
  @override
  void didUpdateWidget(MyStatefulWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget вызван');
    print('Старый title: ${oldWidget.title}');
    print('Новый title: ${widget.title}');
    // Вызывается когда родитель передал новые параметры
  }
  
  @override
  void dispose() {
    super.dispose();
    print('dispose вызван');
  }
  
  @override
  Widget build(BuildContext context) {
    return Text(widget.title);
  }
}

// Использование
class ParentWidget extends StatefulWidget {
  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  String _title = 'Initial';
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        MyStatefulWidget(title: _title),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _title = 'Updated';  // Изменяем входное свойство
            });
          },
          child: const Text('Update'),
        ),
      ],
    );
  }
}

// Вывод:
// 1. Нажимаем "Update"
// 2. Parent вызывает setState
// 3. Parent перестраивает MyStatefulWidget с новым title
// 4. didUpdateWidget вызывается в MyStatefulWidget
// 5. print выведет:
//    "didUpdateWidget вызван"
//    "Старый title: Initial"
//    "Новый title: Updated"

Пример: Фильтрация списка при изменении фильтра

class FilteredListWidget extends StatefulWidget {
  final List<String> items;
  final String filter;
  
  const FilteredListWidget({
    required this.items,
    required this.filter,
  });
  
  @override
  State<FilteredListWidget> createState() => _FilteredListWidgetState();
}

class _FilteredListWidgetState extends State<FilteredListWidget> {
  late List<String> _filteredItems;
  
  @override
  void initState() {
    super.initState();
    _filteredItems = _filterItems();
  }
  
  @override
  void didUpdateWidget(FilteredListWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    
    // Если изменился filter или items, пересчитываем
    if (oldWidget.filter != widget.filter ||
        oldWidget.items != widget.items) {
      _filteredItems = _filterItems();
    }
  }
  
  List<String> _filterItems() {
    return widget.items
        .where((item) => item.contains(widget.filter))
        .toList();
  }
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _filteredItems.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(_filteredItems[index]),
        );
      },
    );
  }
}

// Использование
class SearchScreen extends StatefulWidget {
  @override
  State<SearchScreen> createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {
  final List<String> _allItems = [
    'Apple',
    'Banana',
    'Cherry',
    'Date',
    'Elderberry',
  ];
  String _filter = '';
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          onChanged: (value) {
            setState(() {
              _filter = value;  // Обновляем filter
            });
          },
          decoration: const InputDecoration(hintText: 'Search...'),
        ),
        Expanded(
          child: FilteredListWidget(
            items: _allItems,
            filter: _filter,  // Передаём новый filter
          ),
        ),
      ],
    );
  }
}

Когда пользователь вводит текст в TextField:

  1. setState вызывается в SearchScreen
  2. SearchScreen перестраивается
  3. FilteredListWidget получает новый filter
  4. didUpdateWidget вызывается в FilteredListWidgetState
  5. Список пересчитывается и UI обновляется

Практический пример: Загрузка данных при изменении ID

class UserDetailWidget extends StatefulWidget {
  final String userId;
  
  const UserDetailWidget({required this.userId});
  
  @override
  State<UserDetailWidget> createState() => _UserDetailWidgetState();
}

class _UserDetailWidgetState extends State<UserDetailWidget> {
  late Future<User> _userFuture;
  
  @override
  void initState() {
    super.initState();
    _userFuture = _loadUser();
  }
  
  @override
  void didUpdateWidget(UserDetailWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    
    // Если userId изменился, загружаем нового пользователя
    if (oldWidget.userId != widget.userId) {
      _userFuture = _loadUser();
    }
  }
  
  Future<User> _loadUser() async {
    return await api.getUser(widget.userId);
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: _userFuture,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          final user = snapshot.data!;
          return Column(
            children: [
              Text('Name: ${user.name}'),
              Text('Email: ${user.email}'),
            ],
          );
        } else if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        } else {
          return const CircularProgressIndicator();
        }
      },
    );
  }
}

// Использование
class UserListScreen extends StatefulWidget {
  @override
  State<UserListScreen> createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  String? _selectedUserId;
  
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // Список пользователей
        Expanded(
          flex: 1,
          child: ListView.builder(
            itemCount: 10,
            itemBuilder: (context, index) {
              final userId = 'user_$index';
              return ListTile(
                title: Text('User $index'),
                onTap: () {
                  setState(() {
                    _selectedUserId = userId;
                  });
                },
              );
            },
          ),
        ),
        // Детали пользователя
        Expanded(
          flex: 2,
          child: _selectedUserId != null
              ? UserDetailWidget(userId: _selectedUserId!)
              : const Center(child: Text('Select a user')),
        ),
      ],
    );
  }
}

// Когда пользователь нажимает на другого пользователя:
// 1. setState обновляет _selectedUserId
// 2. UserDetailWidget перестраивается с новым userId
// 3. didUpdateWidget вызывается и начинает загрузку нового пользователя

Сравнение lifecycle методов

class MyState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    // ✅ Вызывается ЛИШ ОДН АЗ при создании State
    // Используй для:
    // - Инициализации переменных
    // - Запуска слушателей
    // - Загрузки начальных данных
  }
  
  @override
  void didUpdateWidget(MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // ✅ Вызывается КАЖДЫЙ РАЗ когда входные параметры изменяются
    // Используй для:
    // - Реагирования на изменения в widget.property
    // - Пересчета состояния на основе новых параметров
    // - Обновления данных при изменении входных значений
  }
  
  @override
  void deactivate() {
    super.deactivate();
    // ✅ Вызывается перед remove из дерева
    // Используй для:
    // - Чистки внешних ресурсов
  }
  
  @override
  void dispose() {
    super.dispose();
    // ✅ Вызывается когда State окончательно удаляется
    // Используй для:
    // - Закрытия контроллеров
    // - Отписки от stream'ов
    // - Очистки памяти
  }
}

Best Practices

✅ ИСПОЛЬЗУЙ didUpdateWidget для:

@override
void didUpdateWidget(MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  
  // 1. Проверь что именно изменилось
  if (oldWidget.searchQuery != widget.searchQuery) {
    _performSearch();
  }
  
  // 2. Перестарт animation'ов
  if (oldWidget.isActive != widget.isActive && widget.isActive) {
    _animationController.forward();
  }
  
  // 3. Обновление кэша или state на основе новых параметров
  if (oldWidget.userId != widget.userId) {
    _loadUserData();
  }
}

❌ НЕ ДЕЛАЙ:

// ❌ Не вызывай setState в didUpdateWidget
@override
void didUpdateWidget(MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  setState(() {  // ❌ ОПАСНО!
    _myVariable = widget.newValue;
  });
}

// ✅ Правильно
@override
void didUpdateWidget(MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  _myVariable = widget.newValue;  // Просто присвой
}

Пример: API с интервальным обновлением

class AutoRefreshWidget extends StatefulWidget {
  final String dataId;
  final Duration refreshInterval;
  
  const AutoRefreshWidget({
    required this.dataId,
    this.refreshInterval = const Duration(seconds: 5),
  });
  
  @override
  State<AutoRefreshWidget> createState() => _AutoRefreshWidgetState();
}

class _AutoRefreshWidgetState extends State<AutoRefreshWidget> {
  late Timer _refreshTimer;
  late Future<Data> _dataFuture;
  
  @override
  void initState() {
    super.initState();
    _startRefresh();
  }
  
  @override
  void didUpdateWidget(AutoRefreshWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    
    // Если dataId или интервал изменились, перезагрузи
    if (oldWidget.dataId != widget.dataId ||
        oldWidget.refreshInterval != widget.refreshInterval) {
      _refreshTimer.cancel();
      _startRefresh();
    }
  }
  
  void _startRefresh() {
    _dataFuture = _loadData();
    _refreshTimer = Timer.periodic(widget.refreshInterval, (_) {
      setState(() {
        _dataFuture = _loadData();
      });
    });
  }
  
  Future<Data> _loadData() async {
    return await api.getData(widget.dataId);
  }
  
  @override
  void dispose() {
    _refreshTimer.cancel();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<Data>(
      future: _dataFuture,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text('Data: ${snapshot.data!.value}');
        }
        return const CircularProgressIndicator();
      },
    );
  }
}

Итог

didUpdateWidget — это:

  • Lifecycle метод вызывается когда входные параметры изменяются
  • Нужен для реагирования на изменения в родительском виджете
  • Essential для виджетов, которые зависят от входных данных
  • Должен быть использован вместо setState для обновления state
  • Хороший способ реализовать реактивное поведение

Это один из самых важных методов для создания корректно работающих StatefulWidget'ов в Flutter.

Что такое didUpdateWidget? | PrepBro