Help us understand the problem. What is going on with this article?

関数コンポネントのライフサイクル使うhook

関数コンポーネントのライフサイクル

Reactの関数コンポーネントにも、mount / unmount の概念が存在します。

useState など (公式の) hooksの戻り値は、react内部の配列のような構造で管理・配分された値です。もし本当にunmountされた後で使うと、Reactのdebug buildは↓のように警告してくれます。production版は試していないが、もし問題になると、このようなミスを探すのが難しいと予想します。

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

async/await とリソース解放

無駄な計算とメモリリークを防止するために、コンポーネントによって配分された各種リソース (開始した非同期タスクなど) は、unmount以降早く解放すべき。

effectの中で割り当てられて、dispose() などAPIを設けてくれたリソースならいいが、async/awaitの処理フローはそうではなく、Promiseオブジェクトが生成された時点からcancelもpauseもできない。

さらに、1箇所にawait使うと、伝染のように以降の処理のタイミング (とリソース配分) もpromiseに依存するようになる。

async/awaitを使わないで、非同期処理のフローをuseEffect()に合わせることはできなくはないが、awaitの回数によってロジック上連続する処理を複数のuseEffectに分けるようになってしまうため、私はそう書きたくない。

あるコード例

こんなコンポーネント作ることとします:

  • あるボタンをクリックすると、signIn() API でログインして (このAPIはPromiseを返す)、返されたユーザ名をUIに表示する
  • ↑ signIn() Promiseがfulfillした時点からの秒数をUIに表示する

version 1

const SignInTimerV1: React.FC = () => {
  const [username, setUsername] = useState(null);
  const [timeSinceLogin, setTimeSinceLogin] = useState(NaN);

  const onClick = async () => {
    const user = await signIn();

    setUsername(user.name);
    const timer = setInterval(() => {
      setTimeSinceLogin(t => t+1000);
    }, 1000);
  };

  return (
    <div>
      <button onClick={onClick} > signIn() </button>
      <p>current user: { username } </p>
      <p>time since sign in: {timeSinceLogin} </p>
    </div>
  );
}

もしsignIn() がfulfillした時点で すでにunmountされてると、setUsernameを呼ぶべきではない。そして、unmountされるときtimerを解放しないとメモリリークになる。

こんなhooks書いた

export function useLifeCycle() {
  const l = useMemo(() => {
    const mounted /* boolean */ = false;
    const unmountCallback /* Function[] */ = [];
    const mountCallback /* Function[] */ = [];
    const unmounted /* Promise<void> */ = new Promise(fulfill => unmountCallback.push(fulfill));
    return {
      mounted,
      unmountCallback,
      mountCallback,
      unmounted,
    };
  }, []);

  useEffect(() => {
    l.mounted = true;
    for (const f of l.mountCallback) f();
    return () => {
      for (const f of l.unmountCallback) f();
      l.mounted = false;
    };
  }, []);

  return l;
}
  • 呼び出し元のコンポーネントのライフサイクル状態 (mounted) を返す
  • classコンポーネントのように、mount時点 (componentDidMount)、unmount時点 (componentWillUnmount) にコールバックを登録できる

version 2

const SignInTimerV2: React.FC = () => {
  const lifecycle = useLifeCycle();                               // changed
  const [username, setUsername] = useState(null);
  const [timeSinceLogin, setTimeSinceLogin] = useState(NaN);

  const onClick = async () => {
    const user = await signIn();

    if (!lifecycle.mounted) return;                               // changed
    setUsername(user.name);
    const timer = setInterval(() => {
      if (lifecycle.mounted) setTimeSinceLogin(t => t+1000);      // changed
    }, 1000);

    lifecycle.unmounted.then(() => clearInterval(timer));         // changed
  };

  return (
    <div>
      <button onClick={onClick} > signIn() </button>
      <p>current user: { username } </p>
      <p>time since sign in: {timeSinceLogin} </p>
    </div>
  );
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした