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

Приведи пример использования локальных ключей

2.0 Middle🔥 201 комментариев
#Flutter виджеты

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

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

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

Локальные ключи (LocalKey) в Flutter

Keys в Flutter — это механизм, который помогает виджетам идентифицировать друг друга при rebuild. LocalKey — это локальный ключ, уникальный только в рамках родительского виджета. Это критично для корректной работы stateful виджетов и анимаций.

Что такое Keys и зачем они нужны?

Когда Flutter пересчитывает дерево виджетов, ему нужно понимать, какой старый виджет соответствует какому новому. Без ключей, Flutter использует тип и позицию виджета, что часто приводит к ошибкам.

// ❌ БЕЗ ключей — проблема
class ItemList extends StatefulWidget {
  @override
  State<ItemList> createState() => _ItemListState();
}

class _ItemListState extends State<ItemList> {
  List<String> items = ["A", "B", "C"];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        for (final item in items)
          StatefulItem(item), // ❌ Без ключей
      ],
    );
  }
}

class StatefulItem extends StatefulWidget {
  final String label;
  const StatefulItem(this.label);

  @override
  State<StatefulItem> createState() => _StatefulItemState();
}

class _StatefulItemState extends State<StatefulItem> {
  late TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.label);
  }

  @override
  Widget build(BuildContext context) {
    return TextField(controller: _controller);
  }
}

// Если ты удалишь первый item ("A"), Flutter переассигнует состояние:
// Старый State<"A"> → Используется для новой "B"
// Результат: текстовое поле покажет "A", хотя должно быть "B"

Типы LocalKey

В Flutter есть несколько типов локальных ключей:

1. ValueKey — самый распространённый

Используется, когда у каждого виджета есть уникальное значение (ID, имя, и т.д.)

class _ItemListState extends State<ItemList> {
  List<String> items = ["A", "B", "C"];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        for (final item in items)
          StatefulItem(
            item,
            key: ValueKey(item), // ✅ Каждый item имеет уникальный ключ
          ),
      ],
    );
  }
}

Практический пример: Список с удалением

class TodoList extends StatefulWidget {
  @override
  State<TodoList> createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  final List<Todo> todos = [
    Todo(id: 1, title: "Buy milk"),
    Todo(id: 2, title: "Call mom"),
    Todo(id: 3, title: "Fix bug"),
  ];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, index) {
        final todo = todos[index];
        return TodoItem(
          key: ValueKey(todo.id), // ✅ Уникальный ID
          todo: todo,
          onDelete: () {
            setState(() {
              todos.removeAt(index);
            });
          },
        );
      },
    );
  }
}

class TodoItem extends StatefulWidget {
  final Todo todo;
  final VoidCallback onDelete;

  const TodoItem({
    required Key key,
    required this.todo,
    required this.onDelete,
  }) : super(key: key);

  @override
  State<TodoItem> createState() => _TodoItemState();
}

class _TodoItemState extends State<TodoItem> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return SlideTransition(
      position: Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0)).animate(_controller),
      child: ListTile(
        title: Text(widget.todo.title),
        trailing: IconButton(
          icon: const Icon(Icons.delete),
          onPressed: () async {
            await _controller.forward();
            widget.onDelete();
          },
        ),
      ),
    );
  }
}

2. ObjectKey — для объектов

Когда значение сложного типа (весь объект)

class Person {
  final int id;
  final String name;

  Person({required this.id, required this.name});
}

class PeopleList extends StatelessWidget {
  final List<Person> people = [
    Person(id: 1, name: "Alice"),
    Person(id: 2, name: "Bob"),
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        for (final person in people)
          PersonCard(
            person: person,
            key: ObjectKey(person), // ✅ Используем сам объект как ключ
          ),
      ],
    );
  }
}

3. UniqueKey — для уникальных элементов

Когда нужен вероятностно-уникальный ключ (редко используется)

class DynamicList extends StatefulWidget {
  @override
  State<DynamicList> createState() => _DynamicListState();
}

class _DynamicListState extends State<DynamicList> {
  List<Widget> items = [];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              items.add(
                ColorBox(
                  key: UniqueKey(), // ✅ Каждый элемент уникален
                ),
              );
            });
          },
          child: const Text("Add Item"),
        ),
        ...items,
      ],
    );
  }
}

class ColorBox extends StatefulWidget {
  const ColorBox({required Key key}) : super(key: key);

  @override
  State<ColorBox> createState() => _ColorBoxState();
}

class _ColorBoxState extends State<ColorBox> {
  late Color color;

  @override
  void initState() {
    super.initState();
    color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: color,
    );
  }
}

Реальный пример: Табы с Stateful контентом

class TabbedContent extends StatefulWidget {
  @override
  State<TabbedContent> createState() => _TabbedContentState();
}

class _TabbedContentState extends State<TabbedContent> {
  int _selectedTab = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            for (int i = 0; i < 3; i++)
              ElevatedButton(
                onPressed: () => setState(() => _selectedTab = i),
                child: Text("Tab $i"),
              ),
          ],
        ),
        // ❌ БЕЗ ключей — состояние смешается
        // if (_selectedTab == 0) const TabContent(title: "Tab 0"),
        // if (_selectedTab == 1) const TabContent(title: "Tab 1"),
        // if (_selectedTab == 2) const TabContent(title: "Tab 2"),

        // ✅ С ключами — правильно
        if (_selectedTab == 0) const TabContent(key: ValueKey(0), title: "Tab 0"),
        if (_selectedTab == 1) const TabContent(key: ValueKey(1), title: "Tab 1"),
        if (_selectedTab == 2) const TabContent(key: ValueKey(2), title: "Tab 2"),
      ],
    );
  }
}

class TabContent extends StatefulWidget {
  final String title;

  const TabContent({required Key key, required this.title}) : super(key: key);

  @override
  State<TabContent> createState() => _TabContentState();
}

class _TabContentState extends State<TabContent> {
  late TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.title);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: InputDecoration(hintText: "Enter text for ${widget.title}"),
    );
  }
}

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

✅ ИСПОЛЬЗУЙ Keys когда:

  1. У тебя есть list of StatefulWidget с операциями (удаление, переупорядочивание)
  2. Нужны анимации при переходах между состояниями
  3. Один из нескольких похожих StatefulWidget видимый (табы, модалки)
  4. Используешь custom layout widgets

❌ НЕ НУЖНЫ Keys когда:

  1. Только StatelessWidget в списке
  2. Список не меняется (или добавляется только в конец)
  3. Каждый элемент имеет уникальный Index и не переупорядочивается

Отладка проблем с Keys

# Включи debug flag в Flutter Inspector
flutter run --verbose

# Смотри вывод и ищи warnings о State не сохраняющихся
# "Duplicate GlobalKey detected this widget tree"
# "The [GlobalKey] generated are not stable"

Итог

LocalKeys — это не опциональная фишка, а essential инструмент для корректной работы со Stateful виджетами. Правильное использование ValueKey спасает от множества багов, связанных с потерей состояния, неправильными анимациями и неожиданным поведением UI при изменении списков.