reactjs 使用初始状态ReactuseState挂接事件处理程序

6tr1vspr  于 2022-12-12  发布在  React
关注(0)|答案(8)|浏览(143)

我还在研究react钩子,但很难发现我做错了什么。我有一个调整面板大小的组件,onmousedown的一个边,我更新了状态的值,然后有一个mousemove的事件处理程序,它使用了这个值,但是在值改变后,它似乎没有更新。
下面是我的代码:

export default memo(() => {
  const [activePoint, setActivePoint] = useState(null); // initial is null

  const handleResize = () => {
    console.log(activePoint); // is null but should be 'top|bottom|left|right'
  };

  const resizerMouseDown = (e, point) => {
    setActivePoint(point); // setting state as 'top|bottom|left|right'
    window.addEventListener('mousemove', handleResize);
    window.addEventListener('mouseup', cleanup); // removed for clarity
  };

  return (
    <div className="interfaceResizeHandler">
      {resizePoints.map(point => (
        <div
          key={ point }
          className={ `interfaceResizeHandler__resizer interfaceResizeHandler__resizer--${ point }` }
          onMouseDown={ e => resizerMouseDown(e, point) }
        />
      ))}
    </div>
  );
});

问题出在handleResize函数上,该函数应使用最新版本的activePoint,该版本应为字符串top|left|bottom|right,但实际上为null

pes8fvy9

pes8fvy91#

如何修复过时的useState

目前,您的问题是您正在阅读过去的值。当您定义handleResize时,它属于该呈现器,因此,当您重新呈现时,事件侦听器不会发生任何变化,因此它仍然从其呈现器读取旧值。
有几种方法可以解决这个问题。首先让我们看看最简单的解决方案。

在作用域中创建函数

鼠标按下事件的事件侦听器将point值传递给resizerMouseDown函数。该值与activePoint的值相同,因此可以将handleResize函数的定义移到resizerMouseDownconsole.log(point)中。由于此解决方案非常简单,它不能说明需要在另一个上下文中访问resizerMouseDown之外的状态的情况。
See the in-scope function solution live on CodeSandbox.

useRef读取未来值

一个更通用的解决方案是创建一个useRef,每当activePoint发生变化时,您就更新它,这样您就可以从任何陈旧的上下文中读取当前值。

const [activePoint, _setActivePoint] = React.useState(null);

// Create a ref
const activePointRef = React.useRef(activePoint);
// And create our custom function in place of the original setActivePoint
function setActivePoint(point) {
  activePointRef.current = point; // Updates the ref
  _setActivePoint(point);
}

function handleResize() {
  // Now you'll have access to the up-to-date activePoint when you read from activePointRef.current in a stale context
  console.log(activePointRef.current);
}

function resizerMouseDown(event, point) {
  /* Truncated */
}

See the useRef solution live on CodeSandbox.

附录

应该注意的是,这些并不是解决此问题的唯一方法,但这些是我的首选方法,因为尽管某些解决方案比其他解决方案要长,但我认为逻辑更清晰。请使用您和您的团队最了解并找到最能满足您特定需求的解决方案;但是不要忘记记录代码的功能。

ki0zmccv

ki0zmccv2#

您可以从setter函数访问当前状态,因此可以使其:

const handleResize = () => {
  setActivePoint(activePoint => {
    console.log(activePoint);
    return activePoint;
  })
};
z3yyvxxp

z3yyvxxp3#

useRef为回调

与安德里亚的方法类似,可以使用useRef来更新事件侦听器的回调本身,而不是useState值。这允许您在一个回调中使用许多最新的useState值,而只有一个useRef
如果使用useRef创建ref,并在每次渲染时将其值更新为handleResize回调,则存储在ref中的回调将始终可以访问最新的useState值,并且handleResize回调将可供任何过时的回调(如事件处理程序)访问。

function handleResize() {
  console.log(activePoint);
}

// Create the ref,
const handleResizeRef = useRef(handleResize);
// and then update it on each re-render.
handleResizeRef.current = handleResize;

// After that, you can access it via handleResizeRef.current like so
window.addEventListener("mousemove", event => handleResizeRef.current());

考虑到这一点,我们还可以将ref的创建和更新抽象到一个自定义钩子中。

示例

See it live on CodeSandbox.

/**
 * A custom hook that creates a ref for a function, and updates it on every render.
 * The new value is always the same function, but the function's context changes on every render.
 */
function useRefEventListener(fn) {
  const fnRef = useRef(fn);
  fnRef.current = fn;
  return fnRef;
}

export default memo(() => {
  const [activePoint, setActivePoint] = useState(null);

  // We can use the custom hook declared above
  const handleResizeRef = useRefEventListener((event) => {
    // The context of this function will be up-to-date on every re-render.
    console.log(activePoint);
  });

  function resizerMouseDown(event, point) {
    setActivePoint(point);

    // Here we can use the handleResizeRef in our event listener.
    function handleResize(event) {
      handleResizeRef.current(event);
    }
    window.addEventListener("mousemove", handleResize);

    // cleanup removed for clarity
    window.addEventListener("mouseup", cleanup);
  }

  return (
    <div className="interfaceResizeHandler">
      {resizePoints.map((point) => (
        <div
          key={point}
          className={`interfaceResizeHandler__resizer interfaceResizeHandler__resizer--${point}`}
          onMouseDown={(event) => resizerMouseDown(event, point)}
        />
      ))}
    </div>
  );
});
9rnv2umw

9rnv2umw4#

const [activePoint, setActivePoint] = useState(null); // initial is null

  const handleResize = () => {
    setActivePoint(currentActivePoint => { // call set method to get the value
       console.log(currentActivePoint);  
       return currentActivePoint;       // set the same value, so nothing will change
                                        // or a different value, depends on your use case
    });
  };
ryhaxcpt

ryhaxcpt5#

这只是对克里斯·布朗尼55的建议的小小补充。
可以实现一个自定义钩子以避免重复此代码,并以与标准useState几乎相同的方式使用此解决方案:

// useReferredState.js
import React from "react";

export default function useReferredState(initialValue) {
    const [state, setState] = React.useState(initialValue);
    const reference = React.useRef(state);

    const setReferredState = value => {
        reference.current = value;
        setState(value);
    };

    return [reference, setReferredState];
}

// SomeComponent.js
import React from "react";

const SomeComponent = () => {
    const [someValueRef, setSomeValue] = useReferredState();
    // console.log(someValueRef.current);
};
tcomlyy6

tcomlyy66#

对于使用typescript的用户,可以使用以下函数:

export const useReferredState = <T>(
    initialValue: T = undefined
): [T, React.MutableRefObject<T>, React.Dispatch<T>] => {
    const [state, setState] = useState<T>(initialValue);
    const reference = useRef<T>(state);

    const setReferredState = (value) => {
        reference.current = value;
        setState(value);
    };

    return [state, reference, setReferredState];
};

并这样称呼它:

const [
            recordingState,
            recordingStateRef,
            setRecordingState,
        ] = useReferredState<{ test: true }>();

并且当您调用setRecordingState时,它将自动更新ref和状态。

rkkpypqq

rkkpypqq7#

你可以使用useEffect钩子,并在每次activePoint改变时初始化事件监听器。这样你可以最大限度地减少代码中不必要的引用。

z31licg0

z31licg08#

当您需要在组件装载时添加事件侦听器时

* 使用,useEffect()挂接 *

我们需要使用useEffect来设置事件监听器和清除相同的事件。
使用效果相依性清单需要有事件行程常式中使用的状态变数。这将确保行程常式不会存取任何过时的事件。
请看下面的例子。我们有一个简单的count状态,当我们点击给定的按钮时,它会递增。Keydown事件监听器会打印相同的状态值。如果我们从依赖列表中删除count变量,我们的事件监听器会打印旧的状态值。

import { useEffect, useState } from 'react';

function App() {
  const [count, setCount] = useState(0);

  const clickHandler = () => {
    console.log({ count });
    setCount(c => c + 1);
  }

  useEffect(() => {
    document.addEventListener('keydown', normalFunction);

    //Cleanup function of this hook
    return () => {
      document.removeEventListener('keydown', normalFunction);
    }
  }, [count])

  return (
    <div className="App">
      Learn
      <button onClick={clickHandler}>Click me</button>
      <div>{count}</div>
    </div>
  );
}

export default App;

相关问题