[flutter] flutter_hooksの使い方を学ぶ。

## はじめに

今回はflutter_hooksについて勉強したので、共有したいと思います！

正直今まで、Riverpodで代替できるし別に使わなくてもいいのでは..
的な曖昧な理解をしていたので、この機に勉強してみした。

## この記事で学べること

* flutter_hooksの使い方
* flutter_hooksを使うシーン
* riverpodとの使い分け

## 導入

### flutter_hooks

https://pub.dev/packages/flutter_hooks

Riverpodを導入していない場合は、基本的にこのpackageを使用します。


### hooks_riverpod

https://pub.dev/packages/hooks_riverpod

もうすでに、flutter_riverpodを導入している場合、
flutter_riverpodの代わりにこのpackageを導入することができます。

※このpackageを導入する際には、
flutter_hooksも同時にimportする必要があります。

:::note warn
注意
このpackageを導入するとflutter_riverpodはimportしなくてもriverpodの機能が使えるようになります。
:::

## flutter_hooksとは

flutter_hooksのpackageを簡潔に説明すると、
アプリの状態管理や、ライフサイクルの処理を、
簡潔にわかりやすく扱うためのpackageになっています。

## Riverpodとの使い分け

変数等の状態を管理するだけなら別に、
Riverpodでよくねって思ってました（真顔）

そもそもRiverpodでは、
**initStateやdisposeのようなライフサイクルを実現できない**ですし(※)、

それを踏まえた上で、**flutter_hooksの状態管理は、
widget内の状態管理に特化した**ものですので、

Riveerpodでの状態管理は,
**globalに複数の画面で変数の状態管理が必要な場合に採用**し、

**Widget内で完結するローカルな状態管理はflutter_hooksで処理し、
使い分けるのが一般的**です。

※（ConsumerStatefulWidgetを使用すれば実現可能ですが、
statefulwidgetを使用しない形での実現は出来ないという意）


## flutter_hooksの使い方

flutter_hookは、statefulwidgetやstatelesswidget,consumerwidgetなどの代わりにHookWidgetを継承します。

```dart
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    // フックの使用
    return Container();
  }
}
```

:::note warn
注意
riverpodの機能も同時に使いたい場合は、hooks_riverpodをimportした上で、HookConsumerWidgetを使用します。
:::

## Hooks機能一覧

今回はPrimitivesと呼ばれる基本的なHooksの機能を紹介します。

* ## useEffect

```dart
void useEffect(
　Dispose? effect(),
　[List<Object?>? keys]
)
```

useEffectにはList<Object?>型ののkeysという引数が設定されており、
このkeysに値を設定すると、**その引数の状態に変化があるたびに、
userEffectの処理が走る仕組み**になっています。

さらに、もう一つの引数であるeffectは、Dispose?型なので、
returnで返り値を指定する必要がありますが、
**クリーンアップ関数と言われており、widgetが破棄されたタイミングで実行**されます。

useEffectひとつで**statefulWidgetのinitStateと、
disposeのライフサイクルを実現できる**ので、
hooksの機能の中でも使用頻度が多い機能です。


以下の使用例のように、keysの配列を空で設定し、
クリーンアップ関数もnullで実装すれば、
画面が構築されたタイミングでのみ処理を走らせることも可能です。


```dart
useEffect(() {
      print('画面が構築されたよ');

      // クリーンアップ関数はここでは必要ありません
      return null; 
    }, []); 
```

* ## useState

riverpodでいう所のproviderのようなもので、変数を生成し、
その変数の状態が変化した際にその状態変更を感知してUIが更新されます。

userStateはbuild内で定義して、そのbuild内で使用するのが一般的です。

以下の例のように初期値を定義して使用することもできますし、
型を指定すれば、初期値を省略することができます。



```dart
class Counter extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final counter = useState(0);

    return GestureDetector(
      // automatically triggers a rebuild of the Counter widget
      onTap: () => counter.value++,
      child: Text(counter.value.toString()),
    );
  }
}
```

* ## useMemoized

```dart
T useMemoized<T>(
T valueBuilder(),
[List<Object?> keys = const <Object>[]]
)
```

計算した結果をキャッシュ化(保存)してくれ、
重い処理や計算を複数回実行する際などに、
前回の計算結果を利用することで、
無駄なレンダリングを抑えることで、**パフォーマンスの向上**を期待できます。

```dart
final test = useMemoized(() => calu(a + b), []);
```
上記のような書き方で、keysの配列を空で設定すると、
calu()という関数は、初回の一度のみ実行され、
二度目以降は、calu()は実行されず、前回の計算結果をそのまま出力する形になります。

```dart
final test = useMemoized(() => calu(a + b), [a,b]);
```
上記のように、keysの配列に,計算に使用する変数を定義しておくと、
aとbの値に変化があった時にのみ、calu()が再実行されます。

* ## useCallback

useMemoizedと似ていて、計算をキャッシュ化してくれるHookです。
useMemoizedとの違いは、useMemoizedが値をキャッシュするのに対して、
**関数自体をキャッシュ化するのがuseCallbackの役割**です。

実際以下のように具体例を提示してみましたが、
ただのカウンターアプリにuseCallbackを採用するのは明らかに、
オーバーエンジニアリングですし、
いつどのタイミングで検討するのかというのが一番肝になりそう。

```dart
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final count = useState(0);

    // useCallbackを使用してイベントハンドラーをメモ化
    final incrementCounter = useCallback(() {
      count.value++;
    }, [count.value]); // 依存関係はcount.value

    return Scaffold(
      appBar: AppBar(title: Text('useCallback Example')),
      body: Center(
        child: Text('Count: ${count.value}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: incrementCounter, // メモ化された関数を使用
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
```

* ## useRef

userRefは、useStateと同じく変数を定義でき状態を管理できるものですが、
useStateとは異なり、**userRefで定義したものの状態が変化しても
UIの再描画が発生することはありません。**

したがって、変数の状態管理はしたいが、
以下のコードのような**UIを再描画させる必要のないシーン**などで、使用します。

```dart
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    // useRefを使用して参照を作成
    final myRef = useRef<int>(0);

    return ElevatedButton(
      onPressed: () {
        // ボタンを押すたびに参照の値を増加させる
        myRef.value++;
        print('Current value is: ${myRef.value}');
      },
      child: Text('Increment'),//UIの変化(再描画)はない。
    );
  }
}
```


* ## useContext

widgetのbuildContextにアクセスするための機能です。

ActionSheetやMediaQueryでサイズを指定する際など、
contextを使用する際に、contextを継承して、、みたいなことをやることが多いですが、
useContextを使用することで直接buildContextにアクセスすることができます。

```dart
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    // useContextを使用してBuildContextを取得
    final buildContext = useContext();

    // コンテキストを使用してテーマデータを取得
    final theme = Theme.of(buildContext);

    return Scaffold(
      appBar: AppBar(title: Text('useContext Example')),
      body: Center(
        child: Text(
          'Current Theme: ${theme.brightness}',
          style: theme.textTheme.headline6,
        ),
      ),
    );
  }
}
```

* ## useValueChanged

useValueChangedは、定義した変数の状態が変化した際に、
その状態変化を契機に任意のロジックを実行するためのHookになります。

以下の例では、countの値が変化した際に、
print('Count has changed to: ${count.value}');が実行されます。


```dart
class MyWidget extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final count = useState(0);

    // useValueChangedを使用してcount.valueの変更を検出
    useValueChanged(count.value, (_, __) {
      print('Count has changed to: ${count.value}');
      // ここで副作用を実行する
      // 例: データのフェッチ、外部リソースのクリーンアップなど
    });

    return Scaffold(
      appBar: AppBar(title: Text('useValueChanged Example')),
      body: Center(
        child: Text('Count: ${count.value}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count.value++,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
```

## 参考資料

https://pub.dev/packages/flutter_hooks#primitives

https://qiita.com/mkosuke/items/f88419d0f4d41ed6d858#useeffect

https://qiita.com/ume-bar/items/13159b93036bd5fe468f

## 終わりに

ちゃんと勉強するとかなり便利で使い勝手良さそうと思いました。


最後までご覧いただきありがとうございます。
何かご指摘等あればコメントいただけると幸いです。