FlutterHooksを気軽に使ってみよう!

14146 ワード

Hooks、便利じゃないですか?

普段、Reactを使っていて、自分はHooks便利だなーと思いながら使っています。

Flutterだと、状態管理の方法として、riverpodやstatefulや、Providerとか
色々ありますが、どれを選んだらいいんやろ?難しいなーと思っている矢先でした。

FlutterにもHooksがあるではないですか!🎉

といっても、わかりやすい使い方が書いてある記事が豊富というわけではなかったので、
Reactを普段業務で使っている自分が、こんな感じで使えそうだよーとか
他の状態管理手法とどう棲み分けさせるか?(個人的にはriverpodと組み合わせやすい)👀
という部分に関して、時分なりの見解をメモできればと思います。

覚えておきたいHooksの関数

以下の2つを最低限覚えておけば問題ないかなと考えています。

あ、あとHooksの関数は基本的に、build()のメソッド内で書くほうが、
個人的には使い勝手良いです。
(他の使い方を知らないだけかもしれません😅)

useState

ページ単位、コンポーネント単位で十分なローカルのステート管理に向いています。

簡単な使い方
final labels = useState<List<String>>([]) // 初期値空配列のstateを作成
final name = useState<String?>(null) // 初期値nullのstateを作成

このように初期化できます。
前者だとnull safetyにstateを管理できます。
後者だと、例えば、api出データ取得するまではnullで、
取得したらvalueに設定するという感じの用途になるかなと思います。
(nullの間はローディングにしておく等の制御をしたい場合)

後ほど、useEffectの方で、使い方の例を載せておきます。

useEffect

ページ、コンポーネント描画時の初回処理等で呼び出される処理をuseEffectで記載します。
StatefulWidgetでのinitStateに感覚は近いでしょうか。
違いとしては以下のような感じで持っておけばいいのかなと思います。

  • initState
    • 初回処理にしか呼ばれない(state変更後のリビルド時には呼ばれない)
  • useEffect
    • 第2引数が空の場合、
      • 初回処理でしか呼ばれない
    • 第2引数に値が入っている場合
      • 初回処理 + 第2引数の値に変更があった場合に呼び出される
簡単な使い方
  • 最初の1回だけ呼び出したい場合
const user = useState<User?>(null);

void getUser() async {
  final api = Api();
  final res = await api.getUser();
  // stateを更新
  user.value = res.data;
}

useEffect((){
  getUser();
}, []);  // 最初の一回だけユーザーを取得したい

return user.value != null ? UserList(user: user) : CircularProgressIndicator(); // userが取れるまではローディング
  • textの値が変わるたびに呼び出したい場合
const text = useState<String>("");
const user = useState<User?>(null);

void getUser() async {
  final api = Api();
  final res = await api.getUser(text.value);
  user.value = res.data;
}

useEffect((){
  getUser();
}, [text]);  // textが更新されたら呼び出す

ここで注意したいのが、第2引数にListのオブジェクトを入れた場合です。
具体例だと以下のようなパターンです。

① 以下のように定義
final list = useState<List<String>>([]);
useEffect((){
  getUser();
}, [list]); 

② listの中身を更新
final newlist = list.value;
newList.value.add("test");
list.value = newList;Listの中身を書き換えたのに、リビルドにuseEffectが走らない!なんでよ!😡

どうやら、変更を検知するのは、オブジェクト自体の変更らしいです。

Listの中身は変わったけど、Listのオブジェクト自体は
同じものを使っているから、「変更が加わった」と検知できない。
ということです。
(Aの箱にリンゴやみかんを入れても、蓋をしたら、前と同じAの箱だね。変更ないね!となってしまう)

これを検知させるには、useStateのvalueに設定する際に、
別のListオブジェクトを生成させてから設定する必要があります。
(Aの箱で初期生成、設定変更時にはBの箱に詰め替えて設定する→箱が変わったね!検知!って感じ)

① 以下のように定義
final list = useState<List<String>>([]);
useEffect((){
  getUser();
}, [list]); 

② listの中身を更新
final newlist = list.value;
newList.value.add("test");
list.value = [...newList]; // スプレッド構文を使って、中の配列を展開し、新しいListオブジェクトを生成

③ リビルド時にuseEffectが走るはず!👍

他にもuseMemorizeやuseStreamもありますが、まずこの2つを使って、
「いやーこの値はキャッシュしたままにしてほしいんだよなー」とか
「ここは状態を管理しておきたいんですよ🤔」

という問題にぶつかったときに調べる、で十分かなと思います。

riverpodとの組み合わせ

ここに関しては、他の方も記事で述べているとこではありますが、

  • グローバルに管理したいstate(ログインしたユーザーの情報、全画面ローディングの状態等、ページ遷移をしても、保持し続けてて欲しいデータ)
    • riverpodで管理
  • ページが切り替わったタイミングで初期化されても問題ないレベルのローカルstate(検索結果画面の、検索結果のデータなど)
    • useStateで管理

この考え方で概ね問題は発生しないかなと思いました。

前に自分は、とにかくriverpodにstateを突っ込んでいましたが、
「これはどこまで影響範囲が及ぶstateなんだ??」という感じで
リファクタしにくかったのですが、
「可能な限りstateの管理範囲は小さくしてコンポーネントを再利用しやすくしたい」
という自分の考えにHooksは相性良かったです☺️

まだ、現時点のFlutterに関しては、この辺の手法が確立していない感があるので、
これを機に、自分はFlutter×Hooksの使い方をもっと試していって、
「この書き方良いで!」というのをみんなに知ってもらえたらいいなと思っています。

あと、もしFlutterについて気になるという方がいたら、
自分も入っている「Flutter大学」というコミュニティがあるので、
よかったら参加してみるのも良いと思います💪