はじめに
今回はflutter_hooksについて勉強したので、共有したいと思います!
正直今まで、Riverpodで代替できるし別に使わなくてもいいのでは..
的な曖昧な理解をしていたので、この機に勉強してみした。
この記事で学べること
- flutter_hooksの使い方
- flutter_hooksを使うシーン
- riverpodとの使い分け
導入
flutter_hooks
Riverpodを導入していない場合は、基本的にこのpackageを使用します。
hooks_riverpod
もうすでに、flutter_riverpodを導入している場合、
flutter_riverpodの代わりにこのpackageを導入することができます。
※このpackageを導入する際には、
flutter_hooksも同時にimportする必要があります。
注意
この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を継承します。
class MyWidget extends HookWidget {
@override
Widget build(BuildContext context) {
// フックの使用
return Container();
}
}
注意
riverpodの機能も同時に使いたい場合は、hooks_riverpodをimportした上で、HookConsumerWidgetを使用します。
Hooks機能一覧
今回はPrimitivesと呼ばれる基本的なHooksの機能を紹介します。
void useEffect(
Dispose? effect(),
[List<Object?>? keys]
)
useEffectにはList型ののkeysという引数が設定されており、
このkeysに値を設定すると、その引数の状態に変化があるたびに、
userEffectの処理が走る仕組みになっています。
さらに、もう一つの引数であるeffectは、Dispose?型なので、
returnで返り値を指定する必要がありますが、
クリーンアップ関数と言われており、widgetが破棄されたタイミングで実行されます。
useEffectひとつでstatefulWidgetのinitStateと、
disposeのライフサイクルを実現できるので、
hooksの機能の中でも使用頻度が多い機能です。
以下の使用例のように、keysの配列を空で設定し、
クリーンアップ関数もnullで実装すれば、
画面が構築されたタイミングでのみ処理を走らせることも可能です。
useEffect(() {
print('画面が構築されたよ');
// クリーンアップ関数はここでは必要ありません
return null;
}, []);
riverpodでいう所のproviderのようなもので、変数を生成し、
その変数の状態が変化した際にその状態変更を感知してUIが更新されます。
userStateはbuild内で定義して、そのbuild内で使用するのが一般的です。
以下の例のように初期値を定義して使用することもできますし、
型を指定すれば、初期値を省略することができます。
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()),
);
}
}
T useMemoized<T>(
T valueBuilder(),
[List<Object?> keys = const <Object>[]]
)
計算した結果をキャッシュ化(保存)してくれ、
重い処理や計算を複数回実行する際などに、
前回の計算結果を利用することで、
無駄なレンダリングを抑えることで、パフォーマンスの向上を期待できます。
final test = useMemoized(() => calu(a + b), []);
上記のような書き方で、keysの配列を空で設定すると、
calu()という関数は、初回の一度のみ実行され、
二度目以降は、calu()は実行されず、前回の計算結果をそのまま出力する形になります。
final test = useMemoized(() => calu(a + b), [a,b]);
上記のように、keysの配列に,計算に使用する変数を定義しておくと、
aとbの値に変化があった時にのみ、calu()が再実行されます。
useMemoizedと似ていて、計算をキャッシュ化してくれるHookです。
useMemoizedとの違いは、useMemoizedが値をキャッシュするのに対して、
関数自体をキャッシュ化するのがuseCallbackの役割です。
実際以下のように具体例を提示してみましたが、
ただのカウンターアプリにuseCallbackを採用するのは明らかに、
オーバーエンジニアリングですし、
いつどのタイミングで検討するのかというのが一番肝になりそう。
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),
),
);
}
}
userRefは、useStateと同じく変数を定義でき状態を管理できるものですが、
useStateとは異なり、userRefで定義したものの状態が変化しても
UIの再描画が発生することはありません。
したがって、変数の状態管理はしたいが、
以下のコードのようなUIを再描画させる必要のないシーンなどで、使用します。
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の変化(再描画)はない。
);
}
}
widgetのbuildContextにアクセスするための機能です。
ActionSheetやMediaQueryでサイズを指定する際など、
contextを使用する際に、contextを継承して、、みたいなことをやることが多いですが、
useContextを使用することで直接buildContextにアクセスすることができます。
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は、定義した変数の状態が変化した際に、
その状態変化を契機に任意のロジックを実行するためのHookになります。
以下の例では、countの値が変化した際に、
print('Count has changed to: ${count.value}');が実行されます。
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),
),
);
}
}
参考資料
終わりに
ちゃんと勉強するとかなり便利で使い勝手良さそうと思いました。
最後までご覧いただきありがとうございます。
何かご指摘等あればコメントいただけると幸いです。