如何在不设置 React 应用程序状态的情况下在组件中执行更改?

如何在不设置 React 应用程序状态的情况下在组件中执行更改?

问题描述:

如果按下左右箭头键,下面的组件将设置状态.因为每个按键都会执行两个设置状态,所以会导致一些性能问题.如何在不设置状态或仅设置一种状态的情况下更改按键?

The component below Will set state if right or left arrow key is pressed. because each pressing key executes two set states, it will leads some performance issues. How can I get change in pressing keys without setting states or with just one set state?

import { useState, useEffect } from "react";


export function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false)
  
  function downHandler({ key }) {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  }
  
  const upHandler = ({ key }) => {
    if (key === targetKey) {
      setKeyPressed(false);
    }
  }
  
  useEffect(() => {
    window.addEventListener("keydown", downHandler);
    window.addEventListener("keyup", upHandler);
    
    return () => {
      window.removeEventListener("keydown", downHandler);
      window.removeEventListener("keyup", upHandler);
    }
  })
  
  return keyPressed
}

如果你总是需要 keyPressed 的当前值,设置状态是没有办法的,但是你可以 通过指定依赖列表来跳过效果 并调用 useRef() 之类的这个:

If you always need the current value of keyPressed, there's no getting around setting state, but you can skip the effect by specifying the dependency list and calling useRef() like this:

export function useEventListener(targetRef, type, handler) {
  const handlerRef = useRef(null);

  useEffect(() => {
    handlerRef.current = handler;
  }, [handler]);

  useEffect(() => {
    const target = targetRef.current;
    const listener = (event) => handlerRef.current(event);

    target.addEventListener(type, listener);

    return () => {
      target.removeEventListener(type, listener);
    };
  }, [targetRef, type]);
}

然后你可以像这样定义你的函数:

And then you can define your function like this:

export function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);
  const windowRef = useRef(window);

  useEventListener(windowRef, 'keydown', ({ key }) => {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  });

  useEventListener(windowRef, 'keyup', ({ key }) => {
    if (key === targetKey) {
      setKeyPressed(false);
    }
  });

  return keyPressed;
}

虽然在上面的特定示例中,targetRef 参数起初可能看起来很迂回,但它非常有用,例如当您想将事件侦听器添加到 JSX 元素时,如下所示:

While the targetRef parameter may seem roundabout at first in the particular example above, it's very useful for instance when you want to add event listeners to JSX elements like this:

const { useEffect, useRef } = React;

function useEventListener(targetRef, type, handler) {
  const handlerRef = useRef(null);

  useEffect(() => {
    handlerRef.current = handler;
  }, [handler]);

  useEffect(() => {
    const target = targetRef.current;
    const listener = (event) => handlerRef.current(event);

    target.addEventListener(type, listener);

    return () => {
      target.removeEventListener(type, listener);
    };
  }, [targetRef, type]);
}

function App() {
  const buttonRef = useRef(null);

  useEventListener(buttonRef, 'click', () => {
    console.log('button clicked');
  });

  return (
    <button ref={buttonRef}>Click Me</button>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>