티스토리 뷰

플러터의 상태 관리로 사용되는 Provider의 Selector 위젯에 대해서 알아보자.

 

Selector는 Provider 패키지에서 제공하는 위젯 중 하나로, 특정 상태의 일부만 구독하여 해당 상태의 변경에 따라 위젯을 업데이트할 수 있도록 도와준다. Provider 패키니에서 제공하는 Consumer 위젯도 상태를 구독하는데, Selector와 달리 상태 전부를 구독한다는 점이 다르다.

 

따라서 상태를 전부 구독해야 한다면 Consuer를 사용하고, 상태의 일부만 구독한다면 Selector를 사용하자.

 

Selector의 사용법은 다음과 같다.

Selector<T, S>(
  selector: (BuildContext context, T value) => S,
  builder: (BuildContext context, S value, Widget? child) {
    // selector의 상태가 변경되면 builder가 호출된다.
    return YourWidget();
  },
)

 

Selector의 T 타입의 상태를 구독하고, S 타입은 구독한 부분을 나타낸다. selector 매개변수에서 T 타입 상태를 받고, 구독하고 싶은 상태인 S를 리턴해야 한다. 이렇게 선택한 S가 변경된다면 builder가 호출된다. builder는 BuildContext와 상태 T의 일부인 S값, child를 전달하니, 해당 값을 기반으로 위젯을 구성하면 된다.

 

예시 코드를 통해 Selector의 사용법에 더 자세히 알아보자. 다음은 Counter의 상태에서 count만 선택하여, count가 변경될 때만 Text 위젯을 리빌드 하는 코드가 된다.

Selector<Counter, int>(
  selector: (context, counter) => counter.count,
  builder: (context, count, child) {
    return Text('Count: $count');
  },
)

 

Selector를 사용하여 특정 상태의 일분만을 감시하고 업데이트할 수 있으므로, 앱의 성능을 최적화하고 불필요한 리빌드를 방지할 수 있다.

 

 

 

selector에서 컬렉션을 사용하는 방법

Selector를 List 타입을 사용해보면, List가 변경되어도 Selector가 원하는대로 작동하지 않는 것을 경험할 수 있다. 그 이유는 Selector가 DeepCollectionEquality를 사용해서 선택한 상태가 변경되었는지 확인을 하기 때문이다. DeepCollectionEquality는 컬렉션의 동등성을 비교하는데 사용되며, 요소들을 재귀적으로 비교한다. 그러나 컬렉션의 요소들이 변경되어도 참조 자체가 변경되지 않으면 DeepCollectionEquality는 요소들이 변경되어도 이를 감지하지 못하다. 즉, 컬렉션의 인스턴스를 새로 만들어서 참조 자체를 바꿔야 한다.

 

따라서 selector에서 컬렉션의 인스턴스를 새로 만들어서 서로 다른 참조가 되도록 변경해서 리턴하도록 해야 한다. 다음 코드는 List<int>인 상태를 selector에서 서로 다른 참조가 되도록, 새로운 List<int>로 만드는 코드가 된다.

Selector<Cart, List<int>>(
  selector: (context, cart) => List<int>.from(cart.list),
  builder: (context, list, child) {
    return Text('Cart List: $list');
  },
)

 

만약 DeepCollectionsEquality가 아닌 다른 방법으로 정의할 수 있도록 Selector는 shouldRebuild 매개변수를 제공한다. shouldRebuild는 2개의 인자를 제공한다. 첫 번째 인자는 이전 결과이고, 두 번째 인자는 새로운 결과이다. 그리고 불리언 값을 반환해야 한다. true를 반환하면 builder가 호출되고, false를 반환하면 builder가 호출되지 않는다. 

 

그러면 shouldRebuild를 이용해서 무조건 true를 반환하면, 상태 변경시 무조건 리빌드가 되게 만들 수 있다. 

Selector<Cart, List<int>>(
  selector: (context, cart) => cart.list,
  shouldRebuild: (old_list, new_list) => true,
  builder: (context, list, child) {
    return Text('Cart List: $list');
  },
)

 

위의 두 가지 방법의 장단점이 다르므로, 프로젝트에서 어떤 방법을 사용할지는 잘 고민하고 선택하자.

댓글
공지사항