web-dev-qa-db-ja.com

useEffectフックでイベントを登録する方法は?

フックを使用してイベントを登録する方法についてUdemyコースをフォローしています。インストラクターは以下のコードを提供しました。

  const [userText, setUserText] = useState('');

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  };

  useEffect(() => {
    window.addEventListener('keydown', handleUserKeyPress);

    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };
  });

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );

今ではそれはうまくいきますが、これが正しい方法であると私は確信していません。その理由は、私が正しく理解すれば、再レンダリングのたびに、イベントが毎回登録および登録解除され続け、それが適切な方法であるとは思えないからです。

だから私は以下のuseEffectフックに少し変更を加えました

useEffect(() => {
  window.addEventListener('keydown', handleUserKeyPress);

  return () => {
    window.removeEventListener('keydown', handleUserKeyPress);
  };
}, []);

2番目の引数として空の配列を使用することにより、コンポーネントにエフェクトを1回だけ実行させ、componentDidMountを模倣します。結果を試してみると、入力するすべてのキーで、追加するのではなく、上書きされるのは奇妙です。

期待していたsetUserText(${userText}${key});新しいタイプのキーを現在の状態に追加して新しい状態として設定するには、代わりに古い状態を忘れて新しい状態で書き換えます。

すべての再レンダリングでイベントを登録および登録解除するのは本当に正しい方法でしたか?

39
Isaac

このようなシナリオに取り組む最善の方法は、イベントハンドラーで何をしているのかを確認することです。以前の状態を使用して状態を設定するだけの場合は、コールバックパターンを使用して、初期マウント時にのみイベントリスナーを登録するのが最善です。 callback patternhttps://reactjs.org/docs/hooks-reference.html#usecallback )を使用しない場合、リスナー参照とそのレキシカルスコープがイベントリスナーですが、新しいレンダリングで新しい関数が更新されたクロージャーで作成されるため、ハンドラーでは更新された状態にできません

const [userText, setUserText] = useState('');

  const handleUserKeyPress = useCallback(event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }, []);

  useEffect(() => {
    window.addEventListener('keydown', handleUserKeyPress);

    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };
  }, [handleUserKeyPress]);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
46
Shubham Khatri

新しい答え:

_useEffect(() => {
  function handlekeydownEvent(event) {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }

  document.addEventListener('keyup', handlekeydownEvent)
  return () => {
    document.removeEventListener('keyup', handlekeydownEvent)
  }
}, [])
_

setUserTextを使用する場合は、オブジェクトではなく関数を引数として渡します。prevUserTextは常に最新の状態になります。


古い答え:

これを試してください、それはあなたの元のコードと同じように動作します:

_useEffect(() => {
  function handlekeydownEvent(event) {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  }

  document.addEventListener('keyup', handlekeydownEvent)
  return () => {
    document.removeEventListener('keyup', handlekeydownEvent)
  }
}, [userText])
_

useEffect()メソッドでは、userText変数に依存しますが、2番目の引数内に配置しないため、userTextは常に最初の引数にバインドされます値_''_と引数_[]_。

このようにする必要はありません。2番目のソリューションが機能しない理由を知らせたいだけです。

3
Spark.Bao

ユースケースでは、useEffectは変更を追跡するための依存関係配列を必要とし、依存関係に基づいて再レンダリングするかどうかを決定できます。依存関係配列をuseEffectに渡すことを常にお勧めします。以下のコードをご覧ください。

useCallbackフックを導入しました。

const { useCallback, useState, useEffect } = React;

  const [userText, setUserText] = useState("");

  const handleUserKeyPress = useCallback(event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }, []);

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [handleUserKeyPress]);

  return (
    <div>
      <blockquote>{userText}</blockquote>
    </div>
  );

Edit q98jov5kvq

1
John Kennedy

以前の状態を追跡する方法が必要です。 useStateは、現在の状態のみを追跡するのに役立ちます。 docs から、別のフックを使用して古い状態にアクセスする方法があります。

const prevRef = useRef();
useEffect(() => {
  prevRef.current = userText;
});

これを使用するようにサンプルを更新しました。そしてそれはうまくいきます。

const { useState, useEffect, useRef } = React;

const App = () => {
  const [userText, setUserText] = useState("");
  const prevRef = useRef();
  useEffect(() => {
    prevRef.current = userText;
  });

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${prevRef.current}${key}`);
    }
  };

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
0
Maaz Syed Adeeb