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

How to execute change in a component without setting state in a react app?

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

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 的当前值,则无法绕过设置状态,但是您 可以 像这样通过 specifying the dependency list and calling useRef() 跳过效果:

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]);
}

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

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 元素添加事件侦听器时,如下所示:

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>