Key의 역할 — GlobalKey, ValueKey, UniqueKey 언제 쓰나

Flutter에서 Key는 위젯의 정체성(identity) 을 결정합니다. 리스트 아이템 재정렬, 애니메이션, Form 상태 유지 등에서 Key가 없으면 예상치 못한 동작이 발생할 수 있습니다.


Key가 필요한 이유

Key가 없으면 Flutter는 위젯의 타입과 위치 로 동일성을 판단합니다.

DART
// Key 없이 리스트 아이템을 삭제하면?
// 첫 번째를 삭제했는데 마지막이 사라지는 것처럼 보일 수 있음

// 문제 상황
Column(
  children: [
    ColorTile(color: Colors.red),    // 삭제
    ColorTile(color: Colors.blue),   // → 위치 0으로 이동
    ColorTile(color: Colors.green),  // → 위치 1로 이동
  ],
)
// Flutter는 위치 기반으로 매칭하므로
// 위치 0의 Element가 blue 위젯을 받아 "red → blue로 변경"으로 처리
// 결과: 내부 상태가 뒤섞임!

Key를 지정하면 Flutter가 위젯의 정체성을 정확히 추적합니다.

DART
Column(
  children: [
    ColorTile(key: ValueKey('red'), color: Colors.red),
    ColorTile(key: ValueKey('blue'), color: Colors.blue),
    ColorTile(key: ValueKey('green'), color: Colors.green),
  ],
)
// Key로 매칭하므로 삭제, 재정렬이 정확하게 동작

Key 종류

ValueKey — 값 기반 식별

고유한 값(ID, 이름 등)이 있을 때 사용합니다. 가장 많이 씁니다.

DART
ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    final todo = todos[index];
    return Dismissible(
      key: ValueKey(todo.id),  // ID로 식별
      child: ListTile(title: Text(todo.title)),
      onDismissed: (_) => _removeTodo(index),
    );
  },
)

ObjectKey — 객체 참조 기반

같은 값이지만 다른 객체를 구분해야 할 때 사용합니다.

DART
ObjectKey(myObject)  // 객체의 참조(주소)로 비교

UniqueKey — 항상 고유

매번 새로운 Key를 생성합니다. 강제로 위젯을 새로 만들고 싶을 때 사용합니다.

DART
// 위젯을 완전히 재생성하고 싶을 때
AnimatedSwitcher(
  child: MyWidget(key: UniqueKey()),  // 항상 새 위젯으로 인식
)

// 주의: build마다 UniqueKey를 생성하면 매번 새 위젯이 됨
// 보통 상태 변경 시에만 새 UniqueKey를 할당

GlobalKey — 전역 식별

위젯 트리 전체에서 고유합니다. 다른 위젯의 State나 RenderObject에 접근할 수 있습니다.

DART
// Form에서 자주 사용
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(validator: ...),
      ElevatedButton(
        onPressed: () {
          // GlobalKey로 Form의 State에 접근
          if (_formKey.currentState!.validate()) {
            _formKey.currentState!.save();
          }
        },
        child: Text('제출'),
      ),
    ],
  ),
)

// Scaffold에서도 활용
final _scaffoldKey = GlobalKey<ScaffoldState>();

Scaffold(
  key: _scaffoldKey,
  body: ...,
)
// _scaffoldKey.currentState!.openDrawer();

Key 사용 가이드

상황추천 Key
리스트 아이템 (ID 있음)ValueKey(item.id)
리스트 아이템 (ID 없음)ObjectKey(item)
DismissibleValueKey(item.id) (필수)
AnimatedSwitcherValueKey(currentValue)
Form 접근GlobalKey<FormState>()
위젯 강제 재생성UniqueKey()

Key가 반드시 필요한 경우

1. 재정렬 가능한 리스트

DART
ReorderableListView(
  children: items.map((item) {
    return ListTile(
      key: ValueKey(item.id),  // 필수!
      title: Text(item.name),
    );
  }).toList(),
  onReorder: (oldIndex, newIndex) {
    setState(() {
      final item = items.removeAt(oldIndex);
      items.insert(newIndex, item);
    });
  },
)

2. Dismissible 위젯

DART
Dismissible(
  key: ValueKey(item.id),  // 필수! Key 없으면 에러
  child: ListTile(title: Text(item.name)),
)

3. AnimatedSwitcher

DART
AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  child: Text(
    '$_count',
    key: ValueKey(_count),  // Key가 달라야 전환 애니메이션 동작
  ),
)

GlobalKey 주의사항

DART
// 나쁜 예: build에서 GlobalKey 생성 (매 빌드마다 새 Key)
@override
Widget build(BuildContext context) {
  final key = GlobalKey();  // 매번 새 Key → 위젯 재생성
  return MyWidget(key: key);
}

// 좋은 예: 필드로 선언
class _MyScreenState extends State<MyScreen> {
  final _formKey = GlobalKey<FormState>();  // 한 번만 생성
  ...
}

면접 포인트: GlobalKey는 비용이 높습니다. 전역적으로 고유성을 보장해야 하고, State에 직접 접근하게 해주기 때문입니다. 꼭 필요한 경우(Form, Navigator 등)에만 사용하세요.


정리

  • Key는 위젯의 정체성 을 결정하여 올바른 Element 매칭을 보장합니다
  • ValueKey: 고유한 값이 있을 때 (가장 흔함)
  • GlobalKey: State나 RenderObject에 직접 접근이 필요할 때
  • UniqueKey: 위젯을 강제로 재생성하고 싶을 때
  • 리스트 재정렬, Dismissible, AnimatedSwitcher에서 Key는 필수입니다
  • GlobalKey는 비용이 높으니 남용하지 마세요
댓글 로딩 중...