ProviderのSelectorを読み解く


# Selectorとは
Flutterでは現在、`ChangeNotifier`を継承したクラスで状態を管理し、`Provider.of`で下位ウィジットでピンポイントに値を取得するというのが状態管理のメジャーな手段かと思われます。

これを用いることで、`notifyListeners()`を呼び出すことで手軽に状態を変更することができますが、
気をつけないと`Provider.of(context, listen: true)`でプロパティを取得しているところ全てでリビルドが発生してしまい、多少無駄な処理が発生することになります。

こんな時は`Selector`を使用することで、指定したプロパティを監視し、値が変化した時のみビルドを発火させることが可能です。
さらに、shouldRebuildを指定することで、監視対象のプロパティが特定の値の時だけビルドを発火させるということも可能です。
では、どのようにしてプロパティを監視しているのか解明するため、`Selector`の実装を見ていきます。

# サンプルコード
ChangeNotifierで2つのカウンターを管理し、counterAに反応するウィジットを考えます。

```dart
class CounterViewer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Selector<CounterState, int>(
      selector: (context, state) => state.counterA, // 監視対象のプロパティを返却
      builder: (context, counter, child) {  // selectorで指定したプロパティが変更されるたびにビルドされる。
        return Text('current count: $counter');
      },
    );
  }
}

class CounterState extends ChangeNotifier {
  int counterA = 0;
  int counterB = 0;
  void incrementA() {
    counterA++;
    notifyListeners();
  }

  void incrementB() {
    counterA++;
    notifyListeners();
  }
}
```

Selectorの実装は以下の通りです。

```dart
class Selector<A, S> extends Selector0<S> {
  /// {@macro provider.selector}
  Selector({
    Key key,
    @required ValueWidgetBuilder<S> builder,
    @required S Function(BuildContext, A) selector,
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })  : assert(selector != null),
        super(
          key: key,
          shouldRebuild: shouldRebuild,
          builder: builder,
          selector: (context) => selector(context, Provider.of(context)),
          child: child,
        );
}

```
Selectorは主に基底クラスの`Selector0`のコンストラクタを呼び出しているだけですが、`selector`に注目です。
`selector`の第二引数には、`Provider.of`で取得した`InheritedWidget`が渡されることがわかります。
そのため、`selector`で`CounterState`の値を扱うことができるわけです。

`Selector0`の実装は以下の通りです。

```dart
class Selector0<T> extends SingleChildStatefulWidget {
  /// Both `builder` and `selector` must not be `null`.
  Selector0({
    Key key,
    @required this.builder,
    @required this.selector,
    ShouldRebuild<T> shouldRebuild,
    Widget child,
  })  : assert(builder != null),
        assert(selector != null),
        _shouldRebuild = shouldRebuild,
        super(key: key, child: child);

  final ValueWidgetBuilder<T> builder;
  final T Function(BuildContext) selector;
  final ShouldRebuild<T> _shouldRebuild;
  @override
  _Selector0State<T> createState() => _Selector0State<T>();
}

class _Selector0State<T> extends SingleChildState<Selector0<T>> {
  T value;
  Widget cache;
  Widget oldWidget;

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    // selectorに渡したメソッドから、監視対象プロパティ(counterA)を取得する。
    final selected = widget.selector(context); 

    // キャッシュしない条件を計算する
    //  - 初回呼び出し時
    //  - shouldRebuildがtrueの時
    //  - selectorで算出したプロパティが変化した時
    var shouldInvalidateCache = oldWidget != widget ||
        (widget._shouldRebuild != null &&
            widget._shouldRebuild.call(value, selected)) ||
        (widget._shouldRebuild == null &&
            !const DeepCollectionEquality().equals(value, selected));
    if (shouldInvalidateCache) {
      // 値をstateにキャッシュする
      value = selected;
      oldWidget = widget;
      // Selectorに渡したbuilderはここで実行される。
      cache = widget.builder(
        context,
        selected,
        child,
      );
    }
    return cache;
  }
}
```

継承元のクラスを見る通り、`Selector`は要するに、子を1つ持つ`StatefulWidget`です。(実際`Selector`にはchildを指定できる)
変化前の値と`builder`をstateに保存しておき、場合に応じてキャッシュした`builder`を返却しています。

結局は`Provider.of`は常に発火するため、`Selector`はリビルドされています。
`Selector`は、プロパティの変化に応じて`builder`で子を新しく作り直すか、キャッシュした子をそのまま返すかを行っているだけです。

# まとめ
- `Selector`の正体は実質`StatefulWidget`である。
- 結局は`Provider.of`は常に発火するため、`Selector`はリビルドされる。
- `Provider.of`による値の変化値を`Selector`でキャッシュしておき、プロパティの変化に応じて新たに`builder`で子を作成し返却もしくは、キャッシュされた子を返却する。
- 結局`Selector`がリビルドされるので、かなり大きいウィジットを`builder`で返さない限り、わざわざ`Selector`でラップする必要性は薄そう。

# 参考
https://pub.dev/documentation/provider/latest/provider/Selector/Selector.html