LoginSignup
1
0

More than 1 year has passed since last update.

useDebounceで学ぶReactのuseEffectでクリーンアップ

Posted at

Debounceについて

最近フロントエンド周りでdebounceという機能を学びました。
debounceは主に検索バーでよく使われる機能ですが、例えば検索バーに入力した値をもとにAPI取得などを行う場合、ユーザがタイピングしている間はAPI取得せず、タイプが終わってからAPI取得を行いたい、というときに便利です。

ただReactそのものにuseDebounceというHooksはないようなので、Reactで使いたいときは自分で実装するか、lodashにあるdebounce関数を使うことになります。

Reactで実装するにしても、カスタムフックを使えば簡単に実装できるので、以下はその例です。

App.js
import * as React from "react";
import useDebounce from "./Debounce";

export default function App() {
  const [input, setInput] = React.useState("");
  const debouncedVal = useDebounce(input, 2000);
  return (
    <div className="App">
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <h3>Normal Input: </h3>
      <p>{input}</p>
      <h3>Debounced Input:</h3>
      <p>{debouncedVal}</p>
    </div>
  );
}

そして、カスタムフックとして実装したuseDebounceがこちら。

Debounce.js
import * as React from "react";

export default function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = React.useState("");

  React.useEffect(() => {
    const timeoutId = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    return () => {
      clearTimeout(timeoutId);
    };
  }, [value]);
  return debouncedValue;
}

useEffect内のreturn () => ???

ちなみにこのdebounceの実装方法を色々調べていて、こういうカスタムフックのuseEffect内でreturnしてタイムアウトをクリアする、というやり方が結構一般的なようでよく使われているようなのですが、useEffect内でreturnするという書き方をまったく知らなかったので、かなり戸惑いました。

ちなみに公式にはこうあります。

Why did we return a function from our effect? This is the optional cleanup mechanism for effects. Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other. They’re part of the same effect!

なぜeffect内でfunctionをreturnするのか?これはeffectをクリーンアップするための任意のやり方です。すべてのeffectにつき、return functionを入れることでその後クリーンアップすることが可能です。こうすることで、ロジックを追加したり、subscriptionを閉じることも両方が可能になります。

さらに、

When exactly does React clean up an effect? React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time. We’ll discuss why this helps avoid bugs and how to opt out of this behavior in case it creates performance issues later below.

Reactは正確にはいつeffectをクリーンアップするのでしょうか?Reactはコンポーネントがアンマウントしたときにクリーンアップを行います。しかし、前述の通り、effectはレンダリングされるたびに動き、そしてそれは一回だけではありません。そのためReactは前回のレンダリングを次にeffectを動かす際にクリーンアップするのです。

とのこと。

Debounceの例で考えたとき、return () => clearTimeout(timeoutId)内ではstateの値が変更されてレンダリングが何度も呼び出されている間、setDebouncedValue(value)でカスタムフック内のstateを書き換えるためのsetTimeoutを呼び出すと同時に、それをまたクリーンアップすることでユーザがタイプをやめない限りタイムアウト(state書き換え)+ クリーンアップがセットで呼び出され続けます。タイプをやめた段階でクリーンアップが呼び出されなくなり、やっとstateが更新されます。

debounceの機能そのものについては、こちらの動画が分かりやすくて好きでした。

API呼び出しでみるクリーンアップ

今度はAPIの例で考えてみたいと思います。

こういったメッセージに遭遇したことがありませんか?

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.

例えば、とある画面でユーザがAPIをリクエストするような何かを行っていた最中に別の画面に遷移していたとします。

特にuseEffect内でAPIのリクエスト呼び出しをしている場合、このreturnを使えばアンマウントしていたらstateへのセットは行わない、ということが出来ます。

App.js
import * as React from "react";
import { Link } from "react-router-dom";

export default function App() {
  const [poke, setPoke] = React.useState([]);

  React.useEffect(() => {
    let unmount = false;

    fetch(" https://pokeapi.co/api/v2/pokemon?limit=10&offset=0")
      .then((res) => res.json())
      .then((res) => {
        if (!unmount) {
          setPoke(res.results);
        }
      });
    return () => {
      unmount = true;
    };
  }, []);
  return (
    <div className="App">
      <Link to="/profile">Profile</Link>
      <ul>
        {poke.map((i) => (
          <li>{i.name}</li>
        ))}
      </ul>
    </div>
  );
}

参考記事:
React useEffect cleanup: How and when to use it
Debouncing with React Hooks

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0