Key의 역할 — GlobalKey, ValueKey, UniqueKey 언제 쓰나
Key의 역할 — GlobalKey, ValueKey, UniqueKey 언제 쓰나
Flutter에서 Key는 위젯의 정체성(identity) 을 결정합니다. 리스트 아이템 재정렬, 애니메이션, Form 상태 유지 등에서 Key가 없으면 예상치 못한 동작이 발생할 수 있습니다.
Key가 필요한 이유
Key가 없으면 Flutter는 위젯의 타입과 위치 로 동일성을 판단합니다.
// 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가 위젯의 정체성을 정확히 추적합니다.
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, 이름 등)이 있을 때 사용합니다. 가장 많이 씁니다.
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 — 객체 참조 기반
같은 값이지만 다른 객체를 구분해야 할 때 사용합니다.
ObjectKey(myObject) // 객체의 참조(주소)로 비교
UniqueKey — 항상 고유
매번 새로운 Key를 생성합니다. 강제로 위젯을 새로 만들고 싶을 때 사용합니다.
// 위젯을 완전히 재생성하고 싶을 때
AnimatedSwitcher(
child: MyWidget(key: UniqueKey()), // 항상 새 위젯으로 인식
)
// 주의: build마다 UniqueKey를 생성하면 매번 새 위젯이 됨
// 보통 상태 변경 시에만 새 UniqueKey를 할당
GlobalKey — 전역 식별
위젯 트리 전체에서 고유합니다. 다른 위젯의 State나 RenderObject에 접근할 수 있습니다.
// 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) |
| Dismissible | ValueKey(item.id) (필수) |
| AnimatedSwitcher | ValueKey(currentValue) |
| Form 접근 | GlobalKey<FormState>() |
| 위젯 강제 재생성 | UniqueKey() |
Key가 반드시 필요한 경우
1. 재정렬 가능한 리스트
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 위젯
Dismissible(
key: ValueKey(item.id), // 필수! Key 없으면 에러
child: ListTile(title: Text(item.name)),
)
3. AnimatedSwitcher
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Text(
'$_count',
key: ValueKey(_count), // Key가 달라야 전환 애니메이션 동작
),
)
GlobalKey 주의사항
// 나쁜 예: 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는 비용이 높으니 남용하지 마세요
댓글 로딩 중...