伊藤  直子

伊藤 直子

1640668102

ReactのuseEffectクリーンアップ機能を理解する

ReactのuseEffectクリーンアップ機能は、エフェクトをクリーンアップすることにより、メモリリークなどの望ましくない動作からアプリケーションを保護します。そうすることで、アプリケーションのパフォーマンスを最適化できます。

この記事を始めるにuseEffectは、APIをフェッチするために使用することを含め、何であるかについての基本的な理解が必要です。この記事では、useEffectフックのクリーンアップ機能について説明します。この記事の終わりまでに、クリーンアップ機能を快適に使用できるようになることを願っています。

useEffectクリーンアップ機能とは何ですか?

名前が示すように、useEffectクリーンアップはフックの関数であり、コンポーネントがアンマウントされる前にコードを整理することができます。コードが実行され、レンダリングごとに再実行されると、クリーンアップ関数を使用してコード自体もクリーンアップされます。useEffect useEffect

useEffectフックは、我々はその中の関数を返すことができますし、クリーンアップが起こるところ、このリターン機能があるような方法で構築されています。クリーンアップ機能は、メモリリークを防ぎ、不要で不要な動作を削除します。

return関数内の状態も更新しないことに注意してください。

useEffect(() => {
        effect
        return () => {
            cleanup
        }
    }, [input])

useEffectクリーンアップ機能が役立つのはなぜですか?

前述のように、useEffectクリーンアップ機能は、開発者が不要な動作を防ぎ、アプリケーションのパフォーマンスを最適化する効果をクリーンアップするのに役立ちます。

ただし、useEffectクリーンアップ関数は、コンポーネントのマウントを解除するときに実行されるだけでなく、次にスケジュールされたエフェクトの実行の直前にも実行されることに注意してください。

実際、エフェクトが実行された後、次にスケジュールされるエフェクトは通常、:に基づいています。dependency(array)

// The dependency is an array
useEffect( callback, dependency )

したがって、効果がプロップに依存している場合、または持続するものを設定する場合はいつでも、クリーンアップ関数を呼び出す理由があります。

このシナリオを見てみましょう。ユーザーのを介して特定のユーザーidのフェッチを取得し、フェッチが完了する前に、考えを変えて別のユーザーを取得しようとするとします。この時点で、id前のフェッチ要求がまだ進行中の間に、小道具、この場合は、が更新されます。

次に、クリーンアップ関数を使用してフェッチを中止し、アプリケーションがメモリリークにさらされないようにする必要があります。

useEffectクリーンアップはいつ使用する必要がありますか?

データをフェッチしてレンダリングするReactコンポーネントがあるとしましょう。promiseが解決する前にコンポーネントがアンマウントされると、useEffect(マウントされていないコンポーネントの)状態を更新しようとし、次のようなエラーを送信します。

警告エラー

このエラーを修正するには、クリーンアップ機能を使用してエラーを解決します。

Reactの公式ドキュメントによると、「Reactは、コンポーネントがアンマウントされたときにクリーンアップを実行します。ただし…エフェクトは、1回だけでなく、すべてのレンダリングに対して実行されます。これが、Reactが次回エフェクトを実行する前に、前のレンダリングのエフェクトもクリーンアップする理由です。」

クリーンアップは通常、行われたすべてのサブスクリプションをキャンセルし、フェッチ要求をキャンセルするために使用されます。それでは、コードを書いて、これらのキャンセルをどのように実行できるかを見てみましょう。

サブスクリプションのクリーンアップ

サブスクリプションのクリーンアップを開始するには、最初にサブスクライブを解除する必要があります。これは、アプリをメモリリークにさらしたくないため、アプリを最適化するためです。

私たちのコンポーネントアンマウント前に、当社のサブスクリプションから退会するには、私たちの変数を設定し、聞かせてisApiSubscribed、とtrueし、我々はそれを設定することができfalse、我々はアンマウントしたいとき:

useEffect(() => {
    // set our variable to true
    let isApiSubscribed = true;
    axios.get(API).then((response) => {
        if (isApiSubscribed) {
            // handle success
        }
    });
    return () => {
        // cancel the subscription
        isApiSubscribed = false;
    };
}, []);

上記のコードでは、変数を設定isApiSubscribedtrueた後、私たちの成功の要求を処理するための条件としてそれを使用します。ただし、コンポーネントをアンマウントisApiSubscribedするfalseときに変数をに設定します。

フェッチリクエストのキャンセル

フェッチリクエストの呼び出しをキャンセルするには、さまざまな方法があります。AxiosのキャンセルトークンAbortController使用するか、使用します

を使用するAbortControllerには、コンストラクターを使用してコントローラーを作成する必要があります。次に、フェッチリクエストが開始されると、リクエストのオブジェクト内にオプションとして渡されます。AbortController()AbortSignaloption

これにより、コントローラーとシグナルがフェッチリクエストに関連付けられ、次を使用していつでもキャンセルできます。AbortController.abort()

>useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

        fetch(API, {
            signal: signal
        })
        .then((response) => response.json())
        .then((response) => {
            // handle success
        });
    return () => {
        // cancel the request before component unmounts
        controller.abort();
    };
}, []);

さらに進んで、catchにエラー条件を追加して、中止したときにフェッチ要求がエラーをスローしないようにすることができます。このエラーが発生するのは、アンマウント中に、エラーを処理するときに状態を更新しようとするためです。

私たちにできることは、条件を記述して、どのようなエラーが発生するかを知ることです。アボートエラーが発生した場合、状態を更新したくありません。

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

   fetch(API, {
      signal: signal
    })
    .then((response) => response.json())
    .then((response) => {
      // handle success
      console.log(response);
    })
    .catch((err) => {
      if (err.name === 'AbortError') {
        console.log('successfully aborted');
      } else {
        // handle error
      }
    });
  return () => {
    // cancel the request before component unmounts
    controller.abort();
  };
}, []);

これで、リクエストが解決する前に焦って別のページに移動した場合でも、コンポーネントがアンマウントされる前にリクエストが中止されるため、そのエラーが再び発生することはありません。アボートエラーが発生した場合、状態も更新されません。

それでは、AxiosのキャンセルオプションであるAxiosキャンセルトークンを使用して同じことを行う方法を見てみましょう。

まず、from Axiosをsourceという名前の定数に格納し、トークンをAxiosオプションとして渡し、次のコマンドでいつでもリクエストをキャンセルします。CancelToken.source()source.cancel()

useEffect(() => {
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();
  axios
    .get(API, {
      cancelToken: source.token
    })
    .catch((err) => {
      if (axios.isCancel(err)) {
        console.log('successfully aborted');
      } else {
        // handle error
      }
    });
  return () => {
    // cancel the request before component unmounts
    source.cancel();
  };
}, []);

AbortErrorinAbortControllerで行ったのと同じように、AxiosはisCancel、エラーの原因を確認し、エラーの処理方法を知ることができるというメソッドを提供します。

Axiosソースが中止またはキャンセルされたためにリクエストが失敗した場合、状態を更新する必要はありません。

useEffectクリーンアップ機能の使い方

上記のエラーが発生する可能性がある場合の例と、発生した場合のクリーンアップ関数の使用方法を見てみましょう。2つのファイルを作成することから始めましょう:PostApp。次のコードを記述して続行します。

// Post component

import React, { useState, useEffect } from "react";
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => setError(err));
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

これは、すべてのレンダリングで投稿を取得し、フェッチエラーを処理する単純な投稿コンポーネントです。

ここでは、メインコンポーネントに投稿コンポーネントをインポートし、ボタンをクリックするたびに投稿を表示します。ボタンは投稿を表示および非表示にします。つまり、投稿コンポーネントをマウントおよびアンマウントします。

// App component

import React, { useState } from "react";
import Post from "./Post";
const App = () => {
  const [show, setShow] = useState(false);
  const showPost = () => {
    // toggles posts onclick of button
    setShow(!show);
  };
  return (
    <div>
      <button onClick={showPost}>Show Posts</button>
      {show && <Post />}
    </div>
  );
};
export default App;

ここで、ボタンをクリックし、投稿がレンダリングされる前にもう一度ボタンをクリックすると(別のシナリオでは、投稿がレンダリングされる前に別のページに移動する場合があります)、コンソールでエラーが発生します。

これは、ReactuseEffectがまだ実行中であり、バックグラウンドでAPIをフェッチしようとしているためです。APIのフェッチが完了すると、状態を更新しようとしますが、今回はマウントされていないコンポーネントであるため、次のエラーがスローされます。

マウントされていないコンポーネントの状態を更新する際のエラーメッセージ

ここで、このエラーをクリアしてメモリリークを停止するには、上記のソリューションのいずれかを使用してクリーンアップ機能を実装する必要があります。この投稿では、以下を使用しますAbortController

// Post component

import React, { useState, useEffect } from "react";
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => {
        setError(err);
      });
    return () => controller.abort();
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

クリーンアップ関数でシグナルを中止した後でも、アンマウントによってエラーがスローされることがコンソールに表示されます。前に説明したように、このエラーはフェッチ呼び出しを中止したときに発生します。

useEffectcatchブロックでフェッチエラーをキャッチしてから、エラー状態を更新しようとすると、エラーがスローされます。この更新を停止するには、条件を使用して、発生したエラーのタイプを確認します。if else

アボートエラーの場合は、状態を更新する必要はありません。更新しない場合は、エラーを処理します。

// Post component

import React, { useState, useEffect } from "react";

export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

      fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => {
        if (err.name === "AbortError") {
          console.log("successfully aborted");
        } else {
          setError(err);
        }
      });
    return () => controller.abort();
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

フェッチを使用する場合とAxiosを使用する場合のメソッドのみを使用する必要があることに注意してくださいerr.name === "AbortError"axios.isCancel()

これで完了です。

結論

useEffect上記の例のように、クリーンアップを必要としないものとクリーンアップを必要とするものの2種類の副作用があります。useEffectフックのクリーンアップ機能をいつどのように使用してメモリリークを防ぎ、アプリケーションを最適化するかを学ぶことは非常に重要です。

この記事がお役に立てば幸いです。クリーンアップ機能を適切に使用できるようになりました。

リンク: https://blog.logrocket.com/understanding-react-useeffect-cleanup-function/

#react 

What is GEEK

Buddha Community

ReactのuseEffectクリーンアップ機能を理解する
伊藤  直子

伊藤 直子

1640668102

ReactのuseEffectクリーンアップ機能を理解する

ReactのuseEffectクリーンアップ機能は、エフェクトをクリーンアップすることにより、メモリリークなどの望ましくない動作からアプリケーションを保護します。そうすることで、アプリケーションのパフォーマンスを最適化できます。

この記事を始めるにuseEffectは、APIをフェッチするために使用することを含め、何であるかについての基本的な理解が必要です。この記事では、useEffectフックのクリーンアップ機能について説明します。この記事の終わりまでに、クリーンアップ機能を快適に使用できるようになることを願っています。

useEffectクリーンアップ機能とは何ですか?

名前が示すように、useEffectクリーンアップはフックの関数であり、コンポーネントがアンマウントされる前にコードを整理することができます。コードが実行され、レンダリングごとに再実行されると、クリーンアップ関数を使用してコード自体もクリーンアップされます。useEffect useEffect

useEffectフックは、我々はその中の関数を返すことができますし、クリーンアップが起こるところ、このリターン機能があるような方法で構築されています。クリーンアップ機能は、メモリリークを防ぎ、不要で不要な動作を削除します。

return関数内の状態も更新しないことに注意してください。

useEffect(() => {
        effect
        return () => {
            cleanup
        }
    }, [input])

useEffectクリーンアップ機能が役立つのはなぜですか?

前述のように、useEffectクリーンアップ機能は、開発者が不要な動作を防ぎ、アプリケーションのパフォーマンスを最適化する効果をクリーンアップするのに役立ちます。

ただし、useEffectクリーンアップ関数は、コンポーネントのマウントを解除するときに実行されるだけでなく、次にスケジュールされたエフェクトの実行の直前にも実行されることに注意してください。

実際、エフェクトが実行された後、次にスケジュールされるエフェクトは通常、:に基づいています。dependency(array)

// The dependency is an array
useEffect( callback, dependency )

したがって、効果がプロップに依存している場合、または持続するものを設定する場合はいつでも、クリーンアップ関数を呼び出す理由があります。

このシナリオを見てみましょう。ユーザーのを介して特定のユーザーidのフェッチを取得し、フェッチが完了する前に、考えを変えて別のユーザーを取得しようとするとします。この時点で、id前のフェッチ要求がまだ進行中の間に、小道具、この場合は、が更新されます。

次に、クリーンアップ関数を使用してフェッチを中止し、アプリケーションがメモリリークにさらされないようにする必要があります。

useEffectクリーンアップはいつ使用する必要がありますか?

データをフェッチしてレンダリングするReactコンポーネントがあるとしましょう。promiseが解決する前にコンポーネントがアンマウントされると、useEffect(マウントされていないコンポーネントの)状態を更新しようとし、次のようなエラーを送信します。

警告エラー

このエラーを修正するには、クリーンアップ機能を使用してエラーを解決します。

Reactの公式ドキュメントによると、「Reactは、コンポーネントがアンマウントされたときにクリーンアップを実行します。ただし…エフェクトは、1回だけでなく、すべてのレンダリングに対して実行されます。これが、Reactが次回エフェクトを実行する前に、前のレンダリングのエフェクトもクリーンアップする理由です。」

クリーンアップは通常、行われたすべてのサブスクリプションをキャンセルし、フェッチ要求をキャンセルするために使用されます。それでは、コードを書いて、これらのキャンセルをどのように実行できるかを見てみましょう。

サブスクリプションのクリーンアップ

サブスクリプションのクリーンアップを開始するには、最初にサブスクライブを解除する必要があります。これは、アプリをメモリリークにさらしたくないため、アプリを最適化するためです。

私たちのコンポーネントアンマウント前に、当社のサブスクリプションから退会するには、私たちの変数を設定し、聞かせてisApiSubscribed、とtrueし、我々はそれを設定することができfalse、我々はアンマウントしたいとき:

useEffect(() => {
    // set our variable to true
    let isApiSubscribed = true;
    axios.get(API).then((response) => {
        if (isApiSubscribed) {
            // handle success
        }
    });
    return () => {
        // cancel the subscription
        isApiSubscribed = false;
    };
}, []);

上記のコードでは、変数を設定isApiSubscribedtrueた後、私たちの成功の要求を処理するための条件としてそれを使用します。ただし、コンポーネントをアンマウントisApiSubscribedするfalseときに変数をに設定します。

フェッチリクエストのキャンセル

フェッチリクエストの呼び出しをキャンセルするには、さまざまな方法があります。AxiosのキャンセルトークンAbortController使用するか、使用します

を使用するAbortControllerには、コンストラクターを使用してコントローラーを作成する必要があります。次に、フェッチリクエストが開始されると、リクエストのオブジェクト内にオプションとして渡されます。AbortController()AbortSignaloption

これにより、コントローラーとシグナルがフェッチリクエストに関連付けられ、次を使用していつでもキャンセルできます。AbortController.abort()

>useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

        fetch(API, {
            signal: signal
        })
        .then((response) => response.json())
        .then((response) => {
            // handle success
        });
    return () => {
        // cancel the request before component unmounts
        controller.abort();
    };
}, []);

さらに進んで、catchにエラー条件を追加して、中止したときにフェッチ要求がエラーをスローしないようにすることができます。このエラーが発生するのは、アンマウント中に、エラーを処理するときに状態を更新しようとするためです。

私たちにできることは、条件を記述して、どのようなエラーが発生するかを知ることです。アボートエラーが発生した場合、状態を更新したくありません。

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

   fetch(API, {
      signal: signal
    })
    .then((response) => response.json())
    .then((response) => {
      // handle success
      console.log(response);
    })
    .catch((err) => {
      if (err.name === 'AbortError') {
        console.log('successfully aborted');
      } else {
        // handle error
      }
    });
  return () => {
    // cancel the request before component unmounts
    controller.abort();
  };
}, []);

これで、リクエストが解決する前に焦って別のページに移動した場合でも、コンポーネントがアンマウントされる前にリクエストが中止されるため、そのエラーが再び発生することはありません。アボートエラーが発生した場合、状態も更新されません。

それでは、AxiosのキャンセルオプションであるAxiosキャンセルトークンを使用して同じことを行う方法を見てみましょう。

まず、from Axiosをsourceという名前の定数に格納し、トークンをAxiosオプションとして渡し、次のコマンドでいつでもリクエストをキャンセルします。CancelToken.source()source.cancel()

useEffect(() => {
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();
  axios
    .get(API, {
      cancelToken: source.token
    })
    .catch((err) => {
      if (axios.isCancel(err)) {
        console.log('successfully aborted');
      } else {
        // handle error
      }
    });
  return () => {
    // cancel the request before component unmounts
    source.cancel();
  };
}, []);

AbortErrorinAbortControllerで行ったのと同じように、AxiosはisCancel、エラーの原因を確認し、エラーの処理方法を知ることができるというメソッドを提供します。

Axiosソースが中止またはキャンセルされたためにリクエストが失敗した場合、状態を更新する必要はありません。

useEffectクリーンアップ機能の使い方

上記のエラーが発生する可能性がある場合の例と、発生した場合のクリーンアップ関数の使用方法を見てみましょう。2つのファイルを作成することから始めましょう:PostApp。次のコードを記述して続行します。

// Post component

import React, { useState, useEffect } from "react";
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => setError(err));
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

これは、すべてのレンダリングで投稿を取得し、フェッチエラーを処理する単純な投稿コンポーネントです。

ここでは、メインコンポーネントに投稿コンポーネントをインポートし、ボタンをクリックするたびに投稿を表示します。ボタンは投稿を表示および非表示にします。つまり、投稿コンポーネントをマウントおよびアンマウントします。

// App component

import React, { useState } from "react";
import Post from "./Post";
const App = () => {
  const [show, setShow] = useState(false);
  const showPost = () => {
    // toggles posts onclick of button
    setShow(!show);
  };
  return (
    <div>
      <button onClick={showPost}>Show Posts</button>
      {show && <Post />}
    </div>
  );
};
export default App;

ここで、ボタンをクリックし、投稿がレンダリングされる前にもう一度ボタンをクリックすると(別のシナリオでは、投稿がレンダリングされる前に別のページに移動する場合があります)、コンソールでエラーが発生します。

これは、ReactuseEffectがまだ実行中であり、バックグラウンドでAPIをフェッチしようとしているためです。APIのフェッチが完了すると、状態を更新しようとしますが、今回はマウントされていないコンポーネントであるため、次のエラーがスローされます。

マウントされていないコンポーネントの状態を更新する際のエラーメッセージ

ここで、このエラーをクリアしてメモリリークを停止するには、上記のソリューションのいずれかを使用してクリーンアップ機能を実装する必要があります。この投稿では、以下を使用しますAbortController

// Post component

import React, { useState, useEffect } from "react";
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => {
        setError(err);
      });
    return () => controller.abort();
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

クリーンアップ関数でシグナルを中止した後でも、アンマウントによってエラーがスローされることがコンソールに表示されます。前に説明したように、このエラーはフェッチ呼び出しを中止したときに発生します。

useEffectcatchブロックでフェッチエラーをキャッチしてから、エラー状態を更新しようとすると、エラーがスローされます。この更新を停止するには、条件を使用して、発生したエラーのタイプを確認します。if else

アボートエラーの場合は、状態を更新する必要はありません。更新しない場合は、エラーを処理します。

// Post component

import React, { useState, useEffect } from "react";

export default function Post() {
  const [posts, setPosts] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

      fetch("https://jsonplaceholder.typicode.com/posts", { signal: signal })
      .then((res) => res.json())
      .then((res) => setPosts(res))
      .catch((err) => {
        if (err.name === "AbortError") {
          console.log("successfully aborted");
        } else {
          setError(err);
        }
      });
    return () => controller.abort();
  }, []);
  return (
    <div>
      {!error ? (
        posts.map((post) => (
          <ul key={post.id}>
            <li>{post.title}</li>
          </ul>
        ))
      ) : (
        <p>{error}</p>
      )}
    </div>
  );
}

フェッチを使用する場合とAxiosを使用する場合のメソッドのみを使用する必要があることに注意してくださいerr.name === "AbortError"axios.isCancel()

これで完了です。

結論

useEffect上記の例のように、クリーンアップを必要としないものとクリーンアップを必要とするものの2種類の副作用があります。useEffectフックのクリーンアップ機能をいつどのように使用してメモリリークを防ぎ、アプリケーションを最適化するかを学ぶことは非常に重要です。

この記事がお役に立てば幸いです。クリーンアップ機能を適切に使用できるようになりました。

リンク: https://blog.logrocket.com/understanding-react-useeffect-cleanup-function/

#react