ネモラムネ

FlutterとReactの状態管理のメモ

#Flutter #React #メモ

2022-12-22

最近Flutter調べてるんだけど、
Flutterの状態管理ライブラリって何だろうとか
ちょっと気になったから調べてみたよ
僕はFlutterのこと全然わからないし使ったこともないから正しいかどうかはわからないよ

Flutterの状態管理を調べていくと、
ReactのRecoilに似てるってあったからこれも調べたよ
メモだから一緒にしとくね

Flutterの状態管理

いくつか読んでみたけどこの記事がわかりやすかったよ
https://zenn.dev/heyhey1028/articles/78ccea1534dcd4

この記事によるとFlutterの状態管理ライブラリは
BLoC -> Provider -> Riverpod
という移り変わりらしいね

あとProviderとRiverpodはバケツリレーを解消するためのDIライブラリのようなもので、
実際に状態管理そのものをするわけではないらしいね

だからProviderやRiverpodは別の状態管理ライブラリと組み合わせて使うみたい。
こんな感じ
「Provider + ChangeNotifier」
「Riverpod + StateNotifier」

ChangeNotifier と StateNotifierのざっくりとした違いは
ChangeNotifierのほうが柔軟で自由で
StateNotifierのほうがシンプルらしいよ

どうやら今の流行はRiverpod+StateNotifierみたいだよ
これがRecoilに似てるらしいね
つまりFlux系らしいよ
https://developers.cyberagent.co.jp/blog/archives/36149/

Flutter Hooksっていうのが相性良いらしいからこれも使うみたいだね
だからよくこういう風に書いてあるよ
StateNotifier + Flutter Hooks + Riverpod

freezed っていうのも使うみたい
これは名前の通りにimmutableなクラスの扱いなどを楽にするライブラリなんだって

BloC

BloCProviderっていうので囲んだ中でBloCが管理する状態にアクセスできるよ
入れ子にしたり、必要な部分だけを囲むこともできるし、
アプリ全体を囲むこともできるみたいだよ。
https://bloclibrary.dev/#/recipesflutterblocaccess?id=global-access

BloCは複雑そうに見えるけど結構シンプルで、
UIイベントをBloCに送ると、BloCが状態を更新するよ
UIは状態を監視してるから、UIも更新されるという感じ

EventとStateを定義して、
そのEventでStateを更新する関数をBloC内に書くっていう感じ

つまりBloCはReducerなんだね

FluxとかMVIにも似てるね

雰囲気はこんな感じ

class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
  ExampleBloc() : super(ExampleState(value: "INIT"));

  @override
  Stream<ExampleState> mapEventToState(ExampleEvent event) async* {
    if (event is FooEvent) {
      yield ExampleState(value: "FOO");
    } else {
      yield ExampleState(value: "OTHER");
    }
  }
}

//Widget内でこうやって使う
BlocBuilder<ExampleBloc, ExampleState>(
    builder: (context, state) => Text(state.value),
),

BloCのライブラリには
Cubitっていうのもあってこれを使うのも便利そうだよ
公式のチュートリアルでも使ってるからこれを使うのが普通っぽいね
Cubitを使うとMVVMっぽくなるよ
どういうことかというとEventを定義するんじゃなくて、
BloCにメソッドを作ってそこで状態を更新するって感じ
シンプルでいい感じだね

雰囲気はこんな感じ

class ExampleCubit extends Cubit<ExampleState> {
  ExampleCubit() : super(ExampleState(value: "INIT"));

  void foo() => emit(ExampleState(value: "FOO"));
  void bar() => emit(ExampleState(value: "OTHER"));
}

ChangeNotifier

ChangeNotifierも結構シンプルな感じだよ
基本的にChangeNotifierを継承したクラスに、
状態のプロパティとメソッドを作ってそこで状態を更新するよ

UIでChangeNotifierを監視できるから、
ChangeNotifierの中にある状態にアクセスできるよ

ChangeNotifierのメソッド内でnotifyListeners()を呼び出すことで更新を通知できるよ
通知するかどうかをメソッド内で決めれるからかなり柔軟に使えそうだね
状態の更新によって通知されるわけじゃないから、
状態がmutableでもimmutableでも大丈夫だよ
一つのChangeNotifierの中に複数状態を定義することもできそうだね

雰囲気はこんな感じ

class ExampleStateChangeNotifier extends ChangeNotifier {
  ExampleStateChangeNotifier() : state = ExampleState();
  ExampleState state;

  void changeState() {
    state = ExampleState()
    notifyListeners();
  }
}

BloCのCubitとかMVVMに似てるね

Providerとかを使ってこれをWidgetツリーに流し込んでいく感じ

StateNotifier

StateNotifierは基本的にはChangeNotifierと同じだよ
ただ状態の持ち方と更新の通知がちょっと違うよ

StateNotifierにはあらかじめstateっていうプロパティが入ってて
これに値を入れることで通知も飛ばせちゃうって物だよ
notifyListeners()を呼び出す必要もないからシンプルだね

雰囲気はこんな感じ

class ExampleStateNotifier extends StateNotifier<ExampleState> {
  ExampleStateNotifier() : super(ExampleState());

  void changeState() {
    state = ExampleState()
  }
}

Provider

Provider自体は状態管理はなくて基本的には
バケツリレーを回避するためのライブラリっぽいよ

ProviderはReactのContextみたいに階層で管理するから弱点がいくつかあるよ
ネストになっちゃったり、
別階層からアクセスできないし、アクセスしようとしたら実行時エラーしちゃったり
Providerが必要なくなった時の破棄が大変だったりするんだって
あと同じ型のProviderを複数同時に利用することができないらしいよ
一つのタイプについて一つのProviderしか使えないんだって
https://medium.com/flutter-jp/state-1daa7fd66b94

でもすごいシンプルなのでライブラリの中で使われてたりしたりするよ

雰囲気こんな感じ

// これで囲む
Provider(
  create: (_) => ExampleState(),
  child: ...
)

//囲まれたところでこうやると取得できる
context.read<ExampleState>()

Riverpod

RiverpodはProviderの作者が弱点を克服するために作ったものなんだって

階層ではなくグローバルスコープに状態を置くので階層のデメリットがないらしいくて
参照がゼロになったら破棄してくれたりするんだって

StateNotifierはもちろんいろいろなProviderがあるみたい
https://riverpod.dev/docs/providers/state_notifier_provider

雰囲気こんな感じ

// アプリをProviderScopeで囲む
void main() {
  runApp(
    const ProviderScope(
      child: ExampleApp(),
    ),
  );
}

// グローバルスコープに状態とかのProviderを宣言する
final exampleStateProvider = StateNotifierProvider(
  (_) => ExampleStateNotifier()
);

//ConsumerWidgetのbuildでこうすると取得できる
ref.watch(exampleStateProvider);

//Hooksを使うとこんな感じで書けてよさそう!
//HookConsumerWidget内
useProvider(exampleStateProvider);

ReactのRecoilに似てるらしいよ

Riverpod 2.0

Riverpod2.0からはNotifierっていうのが内蔵されたから
これを使えばRiverpodだけで状態の変更の通知までできるよ

NotifierはStateNotifierにすごく似てて
stateプロパティを更新することで変更が通知されるよ

こんな感じ

class ExampleNotifier  extends Notifier<ExampleState> {
  @override
  ExampleState build() => ExampleState();

  void changeState() => state = ExampleState();
}

// Providerの宣言
final exampleNotifierProvider = NotifierProvider(ExampleNotifier.new);

riverpod_generatorを使うとNotifierを自動生成することもできて便利だよ
Notifierを自動生成するとProviderも生成してくれるよ

@riverpod
class ExampleNotifier  extends _$ExampleNotifier {
  @override
  ExampleState build() => ExampleState();

  void changeState() => state = ExampleState();
}

// exampleNotifierProviderが自動生成されるよ

Recoil

JavascriptというかReactの状態管理ライブラリ
これもFacebookが作ってるんだって

ReactといえばReduxな感じするよね
Reduxが単一で状態を管理するけど、
RecoilはFluxみたいにバラバラで状態を管理する
グローバルで使えるuseStateって感じ
あと非同期通信に対応してるらしいよ

なんでこれを使うかっていうと
useStateのバケツリレーとか
useContextのプロバイダーのネスト問題を解決するため
Reduxとかほかのライブラリと目的自体は同じだね

atomとかselectorとか用語があるみたい
atomっていうのがStateのことっぽくて
selectorっていうのがReducerみたいな感じで
atomを編集した値を返せるもの
ただselectorも状態の一種らしくて状態を計算した後の状態って感じだよ
なので紐づいてる(計算元)状態が更新されたらselectorも更新されるらしいよ
紐づいてる状態が更新されてもselectorの値が更新されないなら再レンダリングはされないんだって
その辺はさすがしっかりしてるね

familyっていうのもあるらしいね
atomのdefaultを使用時に決めれるように宣言時に引数を渡せるらしいよ

雰囲気こんな感じ

// グローバルスコープにatom(状態)を宣言
const exampleState = atom({
  key: "exampleState",
  default: [],
});

export const ExampleComponent = () => {
  const state = useRecoilState(exampleState);
  return (
    <>
      {state}
    </>
  );
};

Jotai

Recoilに似てるよ
とても軽いことが特徴
Recoilをさらにシンプルにした感じ

Mobx

Recoilに似てるよ
違いとしては、RecoilはReactに依存してるけど
MobXは依存してないから、React以外のプロジェクトでも使えて、Flutterとかでも使えるんだって
https://lealog.hateblo.jp/entry/2020/05/17/021656