86165


useEffectを使用する理由としては下記があると思います。

①レンダリング後に処理を行うことができる  
②useEffectの第2引数で実行タイミングを制御できる  
③アンマウント時のキャンセル処理を設定できる

しかし、逆に言えば上記のいずれも必要ない場合はuseEffectを使用せずに非同期処理を実行してよいということでしょうか？  
例えば、Ajaxでサーバーからデータを取得してコンポーネントに表示する処理などは、下記の理由でuseEffectを使用しなくてよい場合がある気がします。

①Ajaxリクエストの発行はレンダリングの終了を待つ必要がない  
②コンポーネントのレンダリングがReact.memo等で制御されていればuseEffectなしでも実行タイミングを制御できる  
③アンマウント時の処理は特にない

その場合のコードは以下のようになります。

[code]

    const [name, setName] = useState('');
    axios.get(`https://example.com/api/users/XXX`).then(res => setName(res.data.name));
    return (<div>{name}</div>);
    
[/code]


CC BY-SA 4.0
2022-02-04T02:04:11.653
86135
2022-07-17T07:45:12.803
2022-02-05T07:30:50.407
35929
35929
question
javascript reactjs
React Hooksでは非同期処理はuseEffect内で行うことと言われていますが、必ずそうしなければならないのでしょうか？


## 訂正アンサー

**更新日2022年7月17日**

`useEffect` を使う必要はありません。

もともと `useEffect` が奨励されていた理由の一つに、メモリリーク警告が挙げられますが、この警告は誤解を招くとして React18
で削除されました。

コンポーネントがアンマウントした後に、`setState` などしたときに出るメモリリーク警告は、多くのケースでは実際にはメモリリークには繋がりません。

非同期処理は、シンプルに関数を実行する形で問題ないようです。詳しくは、以下のリンクの Dan Abramovさんの解説をご覧ください。

<https://github.com/reactwg/react-18/discussions/82>

## 以下がオリジナルの答え（メモリリーク警告を鵜呑みにした的違いなアンサー）

* * *

`useEffect` を必ず使うべきというわけではありませんが、`useEffect` を使ったほうがベターです。

個人的には、非同期処理をするときに `useCallback` でやっても動けばいいじゃんとも思いますが、`useEffect`
がベスト・プラクティスというのは理解しています。

## サンプルコード

以下のサンプルコードには、大きな問題があります。  
それは、レンダリングごとにリクエストが発生してしまう点です。

[code]

    const [name, setName] = useState('');
    // ↓ コンポーネントがレンダリングされる度に、axios.get が実行される。
    // つまり、レンダリングごとにリクエストが発生する！
    axios.get(`https://example.com/api/users/XXX`).then(res => setName(res.data.name));
    return (<div>{name}</div>);
    
[/code]

  1. 最初の `setName` が実行されたあとに、React の再レンダリング処理が走る
  2. レンダリング時に `axios.get()` が実行される

API サーバーに無駄なリクエストが発生してしまい、これは明らかに問題です。

## `useEffect` を使った場合

`useEffect` を使えば、API リクエストが何度も走ることはありません。

[code]

    const Example = () => {
      const [name, setName] = useState('');
      
      // コンポーネントマウント後に一度だけ実行される
      useEffect(() => {
        let unmounted = false;
    
        async function fetchName() {
          // axios ではなく fetch を使いました
          // https://developer.mozilla.org/ja/docs/Web/API/fetch
          const res = await fetch('https://example.com/api/users/XXX');
          if (!res.ok) {
            throw new Error(`unexpected status: ${res.status}`);
          }
          const data = await res.body();
          // アンマウントされた後に setName するとメモリ開放の妨げになるため、デバッガーに警告が出る
          if (!unmounted) {
            setName(data.name);
          }
        }
    
        fetchName();
    
        return () => {
          unmounted = true;
        };
      }, []);
    
      return <div>{name}</div>;
    };
    
[/code]

## ユーザーアクション後に API リクエストするケース (`useCallback`)

次に、APIリクエストのタイミングが、ボタンクリック後のケースも考えてみましょう。  
下のケースでは、`useCallback` を使います。

[code]

    // ボタンがクリックされたら名前を読み込む
    const Example = () => {
      const [name, setName] = useState('');
      const unmountRef = useRef(false);
    
      useEffect(() => {
        return () => {
          unmountRef.current = true;
        };
      }, []);
      
      const handleClick = useCallback(async () => {
        const res = await fetch('https://example.com/api/users/XXX');
        if (!res.ok) {
          throw new Error(`unexpected status: ${res.status}`);
        }
        const data = await res.body();
        // アンマウントされた後に setName するとメモリ開放の妨げになるため、デバッガーに警告が出る
        if (!unmountRef.current) {
          setName(data.name);
        }
      }, []);
    
      return (
        <div>
          <button onClick={handleClick}>名前を読み込む</button>
          <div>名前：{name}</div>
        </div>
      );
    };
    
[/code]

このケースでは、アンマウント後に `setName` が実行されるのを防ぐため、`useRef` を使ってマウント状態を保存しています。

## ユーザーアクション後に API リクエストするケース (`useEffect`)

かなり冗長的になってしまいますが、理想的な実装です。

[code]

    // ボタンがクリックされたら名前を読み込む
    function reducer(state, action) {
      switch(action.type) {
        case "LOAD":
          return {
            ...state,
            isLoading: true,
          };
        case "SET_NAME":
          return {
            isLoading: false,
            name: action.payload,
          };
        default:
          return state;
      }
    }
    
    function reset() {
      return {
        isLoading: false,
        name: '',
      };
    }
    
    const Example = () => {
      const [state, dispatch] = useReducer(reducer, undefined, reset);
      const { isLoading, name } = state;
    
      useEffect(() => {
        if (!isLoading) {
          return;
        }
    
        let unmounted = false;
    
        async function fetchName() {
          // axios ではなく fetch を使いました
          // https://developer.mozilla.org/ja/docs/Web/API/fetch
          const res = await fetch('https://example.com/api/users/XXX');
          if (!res.ok) {
            throw new Error(`unexpected status: ${res.status}`);
          }
          const data = await res.body();
          // アンマウントされた後に dispatch するとメモリ開放の妨げになるため、デバッガーに警告が出る
          if (!unmounted) {
            dispatch({ type: "SET_NAME", payload: data.name });
          }
        }
    
        fetchName();
    
        return () => {
          unmounted = true;
        };
      }, [isLoading]);
    
      const handleClick = useCallback(async () => {
        dispatch({ type: "LOAD" });
      }, []);
    
      return (
        <div>
          <button onClick={handleClick}>名前を読み込む</button>
          <div>名前：{name}</div>
        </div>
      );
    };
    
[/code]

この最後のケースでは、`isLoading` が `true` になったタイミングで API リクエストを実行し、コンポーネントの状態を更新します。

実装に必要なコードの量は増えますが、コードを読んだときのロジックの把握のしやすさといった、メンテナンス性も考慮すると、`useCallback` よりも
`useEffect` に軍配が上がるかと思います。


CC BY-SA 4.0
2022-02-05T15:10:13.243
86165
2022-07-17T07:45:12.803
2022-07-17T07:45:12.803
51277
51277
86135
answer