본문 바로가기
비전공자를 위한 Flutter/Flutter 심화과정

Flutter How to use Key When, Where, Which key? 언제 어디에 무슨 키를 쓸까?

by 밍잔 2022. 5. 9.

플러터 개발을 하다보면 StatelessWidget, StatefulWidget에서는 초기화 단계에서 항상 key를 받습니다. 하지만 실제로 그 key를 매개변수로 전달한 적은 많이 없었죠. 이전 강의에서 위젯 그대로 트리 안에서 이동만 하는 경우 다시 빌드하는 일을 방지하기 위해 key를 사용하라고 했었죠? 그외에 또 언제, 어디에, 무슨 key를 사용해야 하는지 알아봅시다. 아래는 key에 대해 설명하는 플러터 팀의 동영상입니다.

 

 

[자동자막 > 한글] 설정하고 보세요!

Key의 종류

Local Keys:

1. ValueKey

2. ObjectKey

3. UniqueKey

4. PageStorageKey

 

Global Keys:

1. GlobalKey()

 

설명드리면 로컬키는 위젯 scope안에서 사용하는 키, 글로벌 키는 앱 전역에서 사용될 수 있는 키가 되겠습니다. 

 


키는 언제 써야할까요? 사실 별로 필요없습니다. 3년간 플러터로 앱 개발을 해왔지만, Scaffold의 Drawer를 자식 위젯에서 제어하기 위해 GlobalKey를 몇 번, 앱 디자인에 따라 PageStorageKey를 몇 번 사용한 게 전부였거든요. 플러터 프레임워크 문서에서 설명하는 건 같은 타입의 위젯들을 지우거나, 추가하거나, 재정렬할 때 key를 사용하라고 합니다. 가장 들기 좋은 예로는 Reorderable Listview가 있습니다. 제가 최근에 만든 앱에서 작업할 일이 있었거든요.

 

 

<스타트나우> 할 일 재정렬하기

 

할 일 관리 앱을 만들었는데요. 드래그 드롭을 통해 리스트 순서를 바꿀 수 있습니다. 순서가 바뀔 때 할 일의 내용이 그대로 쓰여진 상태로 순서가 변경되는 게 보이시죠? 네, 여기에 Key를 사용했습니다. ReorderableListView의 리스트 아이템에 Key를 사용하지 않으면, 드래그 앤 드롭을 해도 리스트의 순서가 바뀌지 않는 것을 보실 수 있습니다. 아래는 제 앱에 실제로 작성한 코드입니다.

 

List.generate(
  _reorderableList.length,
  (i) {
    if (_reorderableList[i] is TodoHeader) {
      return ReorderTodoHeader(
        key: Key("header$i"),
        id: _reorderableList[i].id,
        title: _reorderableList[i].title,
        index: _reorderableList[i].index,
        headerColor: _reorderableList[i].color,
      );
    } else if (_reorderableList[i] is Todo) {
      return ReorderTodoItem(
        key: Key("item$i"),
        todo: _reorderableList[i],
      );
    }
    return SizedBox(
      key: Key("$i"),
    );
  },
),

 


Key의 작동 원리

 

 

 

 

이전 트리 구조에서 배웠듯 위젯은 1:1 대응하는 엘리먼트들이 하나씩 있다고 했죠? 엘리먼트 트리는 일종의 뼈대라고 보면 됩니다. 인테리어가 하나도 안 되어있는 구조물인거죠. 그렇기 때문에 아래 그림처럼 실제 위젯의 꾸밈 정보는 위젯 트리를 참조해야 합니다.

 

 

만약 Row 위젯의 자식 위젯들이 바뀌면, 위젯들의 타입이 이전과 일치하는지 엘리먼트 트리를 통해 검사합니다. 1) Row 위젯 자체부터 2) 자식 위젯의 순서로요. 만약 자식 위젯들에게 Key가 있다면 Key까지 검사를 합니다. 만약 Key가 없다면 그냥 타입 일치 여부만 확인하죠. 그럼 자식 위젯이 StatefulWidget인 경우에는 어떨까요? 

 

 

State에 색상정보가 저장은 되지만, 해당 위젯 자체에 저장이 되는 건 아닙니다. Row 위젯의 자식 위젯의 순서를 변경했다고 치죠. 자식 위젯에 Key가 없으면 아까와 마찬가지로 변경 전과 타입이 일치하는지만 검사를 합니다. 그러다보니 위젯 트리의 위젯은 순서가 변경이 됐지만, 엘리먼트 트리는 참조하는 위젯 트리와 타입이 일치하기 때문에 아무런 변화가 일어나지 않았습니다. 그 결과 위젯의 색상은 변경 전 그대로 보이게 됩니다.

 

 

 

위젯에 Key를 사용하면 Row 위젯에서 자식 위젯으로 검사하는 순서가 아니라 Key를 바로 찾는 걸로 검사 절차가 바뀝니다. 타입이 같던 말던 트리 간에 키가 일치하는 위젯을 참조하는 거죠. 

 


Key를 받아야 하는 위젯

 

State에는 애니메이션 정보, 사용자가 입력해서 바뀐 정보, 스크롤 정보 등을 저장하게 됩니다. 그럼 키를 어디에 넣어야 할까요? 바로 State 정보가 필요한 위젯의 최상위 위젯입니다. 그 이유를 알아보죠.

 

 

예를 들어 아까 타일 위젯을 패딩 위젯으로 한 번 감쌌다고 해봅시다. 위젯의 순서가 변경되면 Row 위젯부터 시작해서 자식 위젯으로 타입을 검사한다고 했죠? 위젯 트리와 엘리먼트 트리 매칭 알고리즘은 한 번에 한 층씩 확인합니다.

 

 

첫번째 패딩 위젯과 패딩 엘리먼트는 1:1 매칭이 완벽합니다. 그 다음 타일 위젯을 검사하게 되는데요. 이때 Key가 서로 매치하지 않는다는 사실을 발견하게 됩니다.

 

 

이렇게 자식 위젯의 키가 일치하지 않는 경우 아예 연결을 끊어버립니다. 즉, 특정 계층에서만 매칭여부를 검사한다는 말이 됩니다. 이렇게 되면 기존의 위젯을 unmount하고 새로운 위젯을 하나 다시 만들게 되는 거죠.

 

연결고리 끊고
새로운 위젯 초기화

 

결론은 키를 Tile위젯이 아니라 업데이트가 되는 최상위에 있는 Padding위젯에 주어야만 정상적으로 동작하는 걸 볼 수 있습니다.

 


어떤 Key를 사용해야?

 

1) ValueKey : 어떤 콘텐츠의 내용 자체가 중복되지 않는 경우, 내용 자체가 키가 될 수 있습니다. 텍스트 내용이 키가 되는거죠.

TodoItem(
  key: ValueKey(todo.task),
  todo: todo,
),

 

 

2)ObjectKey : Map과 같은 좀 더 복잡한 데이터 구조를 가진 경우에 적합합니다. 특정 키 밸류값은 일치할 수 있어도, 여러가지 데이터가 조합되면 완전히 일치하기는 쉽지 않죠.

 

 

2)UniqueKey : 위의 2가지도 중복 가능성이 있어서 완전히 독립시키고 싶을 때 이 키를 사용하면 됩니다. 이전에 데이터가 무엇이 들어올지 예측할 수도 없고, 기존에 다른 데이터를 저장하고 있지도 않을 때 적합합니다.

List<Widget> tiles = [
  ColorfulTile(key: UniqueKey()),
  ColorfulTile(key: UniqueKey()),
];

 


Key 사용시 주의할 점

 

바로 키에 랜덤 숫자를 사용하는 겁니다. 키값이 항상 변하기 때문에 키를 사용하는 의미가 없습니다. 어떤 데이터가 저장되는지 먼저 파악하시고 나서 어떤 키를 사용할지 결정하시면 됩니다.

 

 

 

 

 

댓글