Flutter Hooksあれこれ
実環境でFlutter Hooksを使用してだいぶ知見など溜まってきたので、書いていく〜🐶(時間があるときに)
useEffectでよく使うやつ
// keysの指定を忘れないようにできる
void useEffectOnce(Dispose? Function() effect) => useEffect(effect, const []);
// StatefulWidgetの`initState`のように使用できる
void useInitState(void Function() initState) => useEffectOnce(() {
initState();
return null;
});
// StatefulWidgetの`dispose`のように使用できる
void useDispose(void Function() dispose) => useEffectOnce(() => dispose);
useEffectはHookの中でも癖?がある?というか慣れてない人(特にStatefulWidgetのライフサイクルメソッドが理解できてない人)には難しいかもしれないけど、慣れるといい感じ🐶
useMemoizedは大切!(だと思ってる)
// `GlobalKey<FormState>`
GlobalKey<FormState> useFormKey() => useMemoized(GlobalKey<FormState>.new);
StatefulWidgetならStateに保持しておくようなもの(buildのたびに生成されなおされなくてもいいもの)をuseMemoizedを使用して簡単に書ける。
useStateとuseRefの違いも大事。
useStateはValueNotifier<T>
useRefはObjectRef<T>
どちらも使い方は似てる
// 宣言
final index = useState(0);
// useStateの値を変更する
index.value = index.value + 1;
// 宣言
final index2 = useRef(0);
// useRefの値を変更する
index2.value = index2.value + 1;
似てるというか、書き方は一緒。
違いは、stateの変更でrebuildをさせるかさせないか。
useStateはValueNotifierなので値が変更されればChangeNotifierのnotifyListeners()が呼ばれてrebuildが起きる。
それに対してuseRefはObjectRefになっている。
ObjectRefって何??っていうと、ただの値を持ってるクラス。当然値の変更が起きてもrebuildは発生しない。
/// Creates an object that contains a single mutable property.
///
/// Mutating the object's property has no effect.
/// This is useful for sharing state across `build` calls, without causing
/// unnecessary rebuilds.
ObjectRef<T> useRef<T>(T initialValue) {
return useMemoized(() => ObjectRef<T>(initialValue));
}
/// A class that stores a single value.
///
/// It is typically created by [useRef].
class ObjectRef<T> {
/// A class that stores a single value.
///
/// It is typically created by [useRef].
ObjectRef(this.value);
/// A mutable property that will be preserved across rebuilds.
///
/// Updating this property will not cause widgets to rebuild.
T value;
}
useRefの使い道としては、rebuildを起こさなくていい時。
例えば、useEffectでしか値の変更が発生しない場合とか。
(つまりその後にbuild()が呼ばれることがFlutterのライフサイクル上、決まっているので、rebuildさせる必要がない時)。
StatefulWidgetで言えば、initState()やdidUpdateWidget()でしか値の変更をしない場合で、initState()やdidUpdateWidget()の中でsetState()を呼ぶ必要がないのと同じイメージ。
Dart3.0から導入されたRecord型の恩恵!!
/// TabControllerとTabControllerの変化に追従したindexを返す
///
/// `initialLength`にはTabのlengthを入れる
(TabController, int) useTabControllerAndIndex(int initialLength) {
final controller = useTabController(initialLength: initialLength);
final index = useState(controller.index);
// useInitStateがここで登場
useInitState(() {
controller.addListener(() => index.value = controller.index);
});
return (controller, index.value);
}
TabControllerのindexに応じて出し分けたいWidgetがある時とかに使用するかな?
Record型のおかげでカスタムフックが捗る!!
Hookをどこに置くか?問題。
ベーシックにbuild内に書く
class SomeWidget extends HookWidget {
const SomeWidget({super.key});
Widget build(BuildContext context) {
final controller = useScrollController();
useInitState(() {
controller.addListener(() {
someFunction();
});
});
return ...
}
クラスのプライベートメソッドにする
class SomeWidget extends HookWidget {
const SomeWidget({super.key});
Widget build(BuildContext context) {
final controller = _useCustomScrollController();
return ...
}
ScrollController _useCustomScrollController() {
final controller = useScrollController();
useInitState(() {
controller.addListener(() {
someFunction();
});
});
return controller;
}
}
グローバルでプライベートな関数にする
class SomeWidget extends HookWidget {
const SomeWidget({super.key});
Widget build(BuildContext context) {
return const Placeholder();
}
}
ScrollController _useCustomScrollController() {
final controller = useScrollController();
useInitState(() {
controller.addListener(() {
someFunction();
});
});
return controller;
}
どれも問題ないと思うんだけど、どれが良いか悩む。
可読性が悪くない場合はbuild内に直接書いているけど、
長くなって、読みにくい場合に、どうするか...???