flutter_hooksについて解説するよ

# はじめに

今回の記事ではflutter_hooksとriverpodを使って開発してる人向けに解説していきます。

主にflutter_hooksの使い方の解説になります。riverpodについて知りたい方は他の記事を読んでいただいたほうが良いかも知れません！

今回の記事ではflutter_hooksの使い方、実際のプログラム例、riverpodとの併用に関して解説していきます！

# flutter_hooksとは？

https://pub.dev/packages/flutter_hooks

flutter_hooksとは、React Hooksのように`useState` `useEffect` `useCallback` などのフックを使用できるライブラリです。

ちなみに作者の[Remi Rousselet](https://github.com/rrousselGit)さんはriverpodも作成している！ヤバい！
Remiさん的にはflutter_hooksとriverpodを併用することをおすすめしているようです。

flutter_hooksを使うと以下のようなことができます
- 画面に最初から表示しておきたいものを管理できる
- 何らかのアクション・値の変化が発生したときに画面を再描画できる

詳しくは次章から解説していきます！

# プロジェクトへの導入方法

まずはともあれpubspec.ymlに設定を記載していきましょう。

```jsx
dependencies:
  flutter_hooks: ^0.20.5
  hooks_riverpod: ^2.4.10
```

今回はriverpodとの併用を想定しているので、hooks_riverpodも対象となります。

# useXXXって何？

flutter_hooksでは複数のuseXXXという関数が用意されています。

主に使うのは以下の2つです。

### useState

その画面内で利用する「変数」および「変数の値を更新する処理」を定義するために利用する関数です。

### useEffect

初回描画、または特定の値が更新された際に呼び出す処理を記載できる関数です。

# とりあえずカウンターアプリを作ってみる

flutterをインストールしたときに最初に目にするカウンターアプリを使って、実装方法を確認していきましょう

## 例のカウンターアプリを用意する

どこにでもあるカウンターアプリですね。

ポイントとなるのはStatefulWidgetです。現在公式ではStatefulWidgetでの状態管理は非推奨になっているのですが、はじめの第一歩として使ったことのある方は多いでしょう。

今回はこのプログラムをベースにflutter_hooksで書くとどうなるのかをみていきます。

```dart
import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

```

# useStateを使って書き直してみる

StatefulWidgetを使って書いていたカウンターアプリをuseStateを使って書き直してみました。

まずシンプルにコード量が減って読みやすくなっています。

```dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class MyHomePage extends HookWidget {
  final String title;

  const MyHomePage({required this.title, key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final counter = useState<int>(0); # useStateでステート管理したい変数を定義する

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.value}', # .valueの値が変化すると自動的に再描画してくれる
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.value += 1; # ステートを更新する場合.valueを操作する
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

```

## ポイント1: HookWidgetを使おう

```dart
class MyHomePage extends HookWidget {
```

1つ目のポイントは`StatefulWidget`を`HookWidget`に変更した点です。

useXXXを使用したい場合は、`HookWidget`を使ってコンポーネント作成するようにしましょう。

### **※注意点**

ただし、riverpodと併用する場合は「**`HookConsumerWidget`**」を使いましょう

`HookConsumerWidget`は、flutter_hooksとriverpodの両方の機能を組み合わせて使用するためのウィジェットです。

## ポイント2: useStateでステート管理する変数を定義しよう

```dart
final counter = useState<int>(0);
```

上記の例では`counter` という変数をuseStateで定義しています。()の中には初期値をもたせることができます。

以降counter変数がステートを管理してくれるようになります。

## ポイント3: .valueで状態を変更しよう

```dart
onPressed: () {
  counter.value += 1; # ステートを更新する場合.valueを操作する
},
```

`FloatingActionButton` を押したときに`counter.value` を更新しています。

`.value` で実際の値にアクセスすることができるうえに、値を変更することで状態が書き換わります。

状態を変更すると自動的にviewを再描画してくれるので、勝手にカウンターの数値が書き換わってくれます。

# useEffectで初回処理や値変更時の処理を書いてみよう

useStateを使ってカウンターアプリが書けるようになったところで、useEffectを使って初回処理や値変更時の処理を書いてみましょう。

## useEffectを使用した初回処理

```dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class MyHomePage extends HookWidget {
  final String title;

  const MyHomePage({required this.title, key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final counter = useState<int>(0);

    useEffect(() {
      debugPrint("初回処理です");
      return null;
    }, []);

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.value += 1;
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

```

useEffectを使って初回に1度だけ実行される処理を書いてみました。

変わった点は

```dart
useEffect(() {
  debugPrint("初回処理です");
  return null;
}, []);
```

を追加しているだけです。

この記述によって、初回に1度だけ実行される処理を実装することができます。

初回のみにする際はuseEffectの第二引数を[]にする必要があります。

### 何が嬉しいのか？

上記の記述だけだとなんでこんな事するの？って感じだと思います。
普通にbuildメソッドに1行追加するのと何が違うの？という感じですよね。

riverpodを併用する場合、buildメソッドは状態の変更に伴い複数回呼び出されることがあります。
その場合、buildメソッドにただ書いているだけでは初回処理が複数回呼び出される可能性があります。

useEffectはbuild回数に関係なく処理を閉じ込めることができるので、1回だけ呼び出すことが可能になってるわけです。

## 値が変わったときに処理を実行したい

```dart
useEffect(() {
  debugPrint("値が変更されました！！");
  return null;
}, [counter.value]);
```

カウンターアプリで値を変更したときに処理を実行する場合、上記のように実装します。

変更点は第二引数の[]内に監視対象のcounter.valueを追加しているだけです。

これによりcounterの状態が変更したときに特定の処理を実行させることができます。

# 参考文献

https://zenn.dev/flutteruniv/books/flutter-architecture/viewer/6_hooks

https://kimuralog.com/?p=1941

https://qiita.com/takashimelon/items/471e94cac72434cd0f5c

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