🪅

useEffectはinitStateと同じなのか?

2023/10/21に公開

Overview

https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useEffect.html
副作用や、必要に応じて副作用をキャンセルする場合に役立ちます。

useEffect は、キーが指定されていない限り、ビルドごとに同期的に呼び出されます。 この場合、キー内の値が変更された場合にのみ useEffect が再度呼び出されます。

エフェクト コールバックを受け取り、それを同期的に呼び出します。 そのエフェクトはオプションで関数を返すことができ、その関数はエフェクトが再度呼び出されたとき、またはウィジェットが破棄されたときに呼び出されます。

デフォルトでは、キーが指定されていない限り、エフェクトはビルド呼び出しごとに呼び出されます。 この場合、effect は最初の useEffect 呼び出しで 1 回呼び出され、キー内の何かが変更されるたびに呼び出されます。

次の例では、useEffect を呼び出して Stream をサブスクライブし、ウィジェットが破棄されたときにサブスクリプションをキャンセルします。 また、ストリームが変更されると、前のストリームでのリスニングがキャンセルされ、新しいストリームでのリスニングが行われます。

summary

内部を見に行ってみた!
同じようには見える???

void useEffect(Dispose? Function() effect, [List<Object?>? keys]) {
  use(_EffectHook(effect, keys));
}

class _EffectHook extends Hook<void> {
  const _EffectHook(this.effect, [List<Object?>? keys]) : super(keys: keys);

  final Dispose? Function() effect;

  
  _EffectHookState createState() => _EffectHookState();
}

class _EffectHookState extends HookState<void, _EffectHook> {
  Dispose? disposer;

  
  void initHook() {
    super.initHook();
    scheduleEffect();
  }

  
  void didUpdateHook(_EffectHook oldHook) {
    super.didUpdateHook(oldHook);

    if (hook.keys == null) {
      disposer?.call();
      scheduleEffect();
    }
  }

  
  void build(BuildContext context) {}

  
  void dispose() => disposer?.call();

  void scheduleEffect() {
    disposer = hook.effect();
  }

  
  String get debugLabel => 'useEffect';

  
  bool get debugSkipValue => true;
}

initHookなるものを見つけた?

[HookState] の [State.initState] と同等と書いてある?

 /// Equivalent of [State.initState] for [HookState].
  
  void initHook() {}

この記事と同じことをしてみた!
https://zenn.dev/joo_hashi/articles/211dabe9e0b4d2

useEffectを使用したパターン

これは失敗する例です。どうやら、contextを参照できない問題は同じようです。

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

class HookOnCallbackExample extends HookWidget {
  const HookOnCallbackExample({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // スナックバーを出すだけのメソッド
    void snackBar() {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('initStateでcontextを参照'),
        ),
      );
    }
    /* useEffectの第二引数に空の配列を渡すことで、
    initStateのように一度だけ実行することができる
    */
    useEffect(() {
      snackBar();
      // return nullにするのは、useEffectの第二引数に空の配列を渡した場合に必要
      return null;
    }, const []);

    return Scaffold(
      appBar: AppBar(
        title: const Text('addPostFrameCallback'),
      ),
    );
  }
}

ビルドしたらエラーでた!!!!

コードを修正すると同じ動作をしてくれた。最近仕事でみたコードと似ている気がする???

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

class HookOnCallbackExample extends HookWidget {
  const HookOnCallbackExample({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    // スナックバーを出すだけのメソッド
    void snackBar() {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('initStateでcontextを参照'),
        ),
      );
    }
    /* useEffectの第二引数に空の配列を渡すことで、
    initStateのように一度だけ実行することができる
    */
    useEffect(() {
      // useEffectの中でcontextを参照するために、addPostFrameCallbackを使う
      WidgetsBinding.instance.addPostFrameCallback((_) {
        snackBar();
      });
      // return nullにするのは、useEffectの第二引数に空の配列を渡した場合に必要
      return null;
    }, const []);

    return Scaffold(
      appBar: AppBar(
        title: const Text('useEffectの中で呼び出す!'),
      ),
    );
  }
}

できました🙌

thoughts

使ってみた感想ですが、initStateと同じように見えてそうではなさそうな気もします。まだ使い方よくわかっていないので、これから深掘りしていけたらいいなと考えております。

Discussion