Flutter Hooks

今回は、Flutter Hooks の利点と、主に 2 つのフックである useState と useEffect について見ていきます。

まず最初に、UI を再構築することができる、ネイティブの Flutter の setState についてお話ししたいと思います。

setState はウィジェットを再構築する関数で、UI の変化を表示するために Stateful ウィジェットで使用されます。

  • このGIFを見てみましょう。タイマーがバーの
    長さと位置を常にチェックして、setState
    使って UI を再構築しています。
  • しかし、setState は他のウィジェットも再構築
    してしまいます。
Widget buildTextWithImages(String text) 
{
    print("loop");
 
 /////////////////////////////////
110 I/flutter ( 3596): loop
  • まず最初に、タイマーによって他のウィジェットが再構築されるのを防ぐにはどうすればよいでしょうか?
  • 他のウィジェットが再構築されるのを防ぐために、ValueNotifierを使います。
  • late double timerValuelate ValueNotifier<double> timerValueNotifier に変更します。
  • ここでは初期化時に、
    timerValueNotifier =
    ValueNotifier<double>(1.0)と代入して

    います。
  • ValueNotifier の値にアクセスするには、
    timerValueNotifier.value を使います。
  • ウィジェット内でtimerValueNotifier
    を使うには、ValueListenableBuilder
    を使って、ノーティファイアを監視し、
    その値に応じて UI を構築する必要があります。
  • ValueListenableBuilder でラップされ
    たウィジェットのみが値の変化に応じて
    再構築され、ウィジェットツリー全体や
    親ウィジェットは再構築されません。これに
    対してsetStateStatefulWidget 全体
    を再構築するため、関係のない子ウィジェット
    まで影響を受ける可能性があります。

  • これらすべてが Flutter Hooks とどう関係しているのでしょうか?
  • Spotify の UI を見てみましょう。ここに
    ある各ボタンは、それぞれの状態を更新し、
    アイコンを変更しています。
  • つまり、これを実現しようとすると、
    コード内に複数の
    ValueListenableBuilder
    ValueNotifierを使う必要があり、結果として
    コードがごちゃごちゃになってしまいます。
  • これを防ぐために、flutter_hooks というパッケージを使うことができます。このパッケージには、便利なフックである useState が用意されています。
  • useState の中身を見てみると、内部でValueNotifier が使われていることが分かります。
  • useState を使うには、型と初期値を
    指定する必要があります。
  • useState の変数は ValueNotifier
    同じように使い、.value プロパティで
    値にアクセスします。
  • UI では .value プロパティを直接使う
    ことができます。
  • 補足:カスタムフックのコントローラーを
    使っているため、meanings.value
    meanings としてエクスポートする必要が
    あります
  • useState を使うことで、ValueListenableBuilderValueNotifier を使う必要がなくなり、不必要なボイラープレートを避けることができます。
  • もうひとつ非常に便利なフックが useEffect で、これは Flutter のネイティブな initState の代わりとして使うことができます。
  • initState と同様に、useEffect は依存配列を空にすると、初回読み込み時に初期化処理を行うのに役立ちます。
final count = useState(0);

    // 'count.value' が変わるたびに実行される副作用
    useEffect(() {
      // このコードは初回レンダー時と count.value が変わるたびに実行される
      print('Count changed: ${count.value}');

      // ここで API コールなどの他の副作用を行うこともできる

      // オプションのクリーンアップ関数(今回は不要)
      return null;
    }, [count.value]); // 依存配列に count.value を含める
  • しかし、initState と違って、useEffect には依存配列があり、それによってプロジェクト内で副作用を管理することができます。
  • この useEffectisCurrent が変わる
    たびに実行されます。isCurrent
    true のときだけ、
    viewController.loadRealm() をマイク
    ロタスクで非同期に呼び出します。これにより、
    同期処理とぶつからず安全に動かせます。
    クリーンアップは不要なので、null を返して
    います。こうして、isCurrent の状態に応じて
    副作用を実行できます。
  • useStateuseEffect 以外にも多くのフックがあります。以下は、それぞれのフックとその用途を簡単にまとめた表です。
フック名説明典型的な使用例
useMemoized計算結果を依存関係が変わるまでキャッシュします。高コストな計算、オブジェクトの再利用など。
useCallbackコールバック関数を依存関係が変わるまでメモ化します。不必要な再ビルドの防止。
useRef再ビルド間で値を保持しますが、再ビルドのトリガーにはなりません。ミューテーブルな参照、コントローラのキャッシュなど。
usePrevious変数の直前の値を保持・返却します。値の変化検出、トランジション処理、デバッグなど。
useMemoisationuseMemoized の旧名(非推奨)。useMemoized と同様の用途。
useStreamストリームを購読し、最新の AsyncSnapshot を返します。Firebase などのリアルタイムデータ処理。
useFutureFuture を購読し、その AsyncSnapshot を返します。一度だけの非同期データ取得。
useListenableListenable(例: ValueNotifier, AnimationController)を購読。コントローラからのリアクティブな更新。
useValueNotifierValueNotifier を生成して監視します。軽量な状態管理に適しています。
useTextEditingControllerTextEditingController を生成・破棄の管理も行います。テキストフィールドの管理。
useAnimationControllerAnimationController を生成・破棄します。カスタムアニメーションの制御。
useTabControllerTabController を初期化します。タブビューの操作。
useFocusNodeFocusNode を生成・管理します。キーボードフォーカスの制御。
useScrollControllerScrollController を生成・管理します。スクロール位置の監視や操作。
useTickerフレーム単位でコールバックを受け取る Ticker を提供します。低レベルのアニメーション制御。