React 不更新 State (React-Hooks)

React does not update State (React-Hooks)

我有以下设置:

  const templates = [
    'dot',
    'line',
    'circle',
    'square',
  ];
  
  const [currentTemplate, setCurrentTemplate] = useState(0);

  const changeTemplate = (forward = true) => {
    let index = currentTemplate;
    index = forward ? index + 1 : index - 1;
    if (index > templates.length - 1) {
      index = 0;
    } else if (index < 0) {
      index = templates.length - 1;
    }
    setCurrentTemplate(index);
  };

  useEffect(() => {
    console.log(`Current Template is: ${templates[currentTemplate]}`);
  }, [currentTemplate]);

  useKeypress('ArrowLeft', () => {
    changeTemplate(false);
  });
  useKeypress('ArrowRight', () => {
    changeTemplate(true);
  });

这个是useKeypress-Hook的用处:

  import { useEffect } from 'react';
/**
 * useKeyPress
 * @param {string} key - the name of the key to respond to, compared against event.key
 * @param {function} action - the action to perform on key press
 */
export default function useKeypress(key: string, action: () => void) {
  useEffect(() => {
    function onKeyup(e: KeyboardEvent) {
      if (e.key === key) action();
    }
    window.addEventListener('keyup', onKeyup);
    return () => window.removeEventListener('keyup', onKeyup);
  }, []);
}

每当我按下向左或向右箭头键时,该功能就会被触发。但是 currentTemplate 变量没有改变。它始终保持在 0。 useEffect 仅在我从左向右或以其他方式切换键时触发。单击同一个键两次,不会再次触发 useEffect,但它应该!从右向左变化时,输出为Current Template is: square,从左向右变化时,输出为Current Template is: line。但这永远不会改变。 currentTemplate 的值始终保持 0.

我错过了什么?

问题出在 useKeypress 挂钩内的 useEffect

export default function useKeypress(key: string, action: () => void) {
  useEffect(() => {
    function onKeyup(e: KeyboardEvent) {
      if (e.key === key) action();
    }
    window.addEventListener('keyup', onKeyup);
    return () => window.removeEventListener('keyup', onKeyup);
  }, []); //  problem
}

该挂钩是单个 useEffect,没有依赖项。意思是,它只会触发一次。这是您传递给此挂钩的 action 的问题。每当组件中的 changeTemplate-action 发生变化时(即在重新渲染时),对 this 的引用不会在 useKeypress-hook 内部更新。

要解决这个问题,需要将action添加到useEffect的依赖数组中:

export default function useKeypress(key: string, action: () => void) {
  useEffect(() => {
    function onKeyup(e: KeyboardEvent) {
      if (e.key === key) action();
    }
    window.addEventListener('keyup', onKeyup);
    return () => window.removeEventListener('keyup', onKeyup);
  }, [action]); //  whenever `action` changes, the effect will be updated
}

这是为了确保只要给定的 action 发生变化,效果就会更新。进行此更改后,事情应该会按预期进行。

正在优化:

此时将为每个渲染重新生成 useEffect,因为将为每个渲染实例化 changeTemplate 函数。为避免这种情况,您可以用 useCallback 包裹 changeTemplate,以便记忆函数:

const changeTemplate = useCallback(
  (forward = true) => {
    let index = currentTemplate;
    index = forward ? index + 1 : index - 1;
    if (index > templates.length - 1) {
      index = 0;
    } else if (index < 0) {
      index = templates.length - 1;
    }
    setCurrentTemplate(index);
  }, 
  // Regenerate the callback whenever `currentTemplate` or `setCurrentTemplate` changes
  [currentTemplate, setCurrentTemplate]
);