使用初始状态react useState 钩子事件处理程序

IT技术 javascript reactjs ecmascript-6 react-hooks
2021-02-02 23:46:13

我仍然在思考react-hooks,但努力想知道我在这里做错了什么。我有一个用于调整面板大小的组件,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.

6个回答

useRef 读取未来值

目前,您的问题是您正在读取过去的值。当您定义handleResize它属于该渲染时,因此,当您重新渲染时,事件侦听器没有任何react,因此它仍然从其渲染中读取旧值。

要解决此问题,您应该使用useRef不断更新的 ref via ,以便您可以读取当前值。

示例(链接到 jsfiddle):

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

  // define a ref
  const activePointRef = React.useRef(activePoint);

  // in place of original `setActivePoint`
  const setActivePoint = x => {
    activePointRef.current = x; // keep updated
    _setActivePoint(x);
  };

  const handleResize = () => {
    // now when reading `activePointRef.current` you'll
    // have access to the current state
    console.log(activePointRef.current);
  };

  const resizerMouseDown = /* still the same */;

  return /* return is still the same */
@derekliang OP 不想这样setActivePointhandleResize这会导致屏幕更新太多。这样,setActivePoint只调用一次,resizerMouseDown但登录handleResize与重新渲染无关。
2021-03-30 23:46:13
这个解决方案太吵了。我喜欢改用 setActivePoint 方法(参见其他答案)
2021-03-31 23:46:13
如果状态不改变,它不会更新屏幕。它将导致状态更改检查但无法看到更改,因此屏幕没有更新。
2021-04-09 23:46:13
我和OP有同样的问题,但成倍增加。我有几个事件处理程序,它们使用几个不同的状态,它们调用其他几个内部函数。这是在以前基于类的组件上,我正在切换到功能性的,因为我想要useEffect. 无论如何,整个情况对我来说感觉很糟糕。令人惊讶的是,一个状态可能完全能够获得旧值。useRef+statePiece.current修复仍是最好的解决方案?
2021-04-09 23:46:13
我说错了,我的意思是它会强制更新 React。虽然 React 知道不重新渲染到 DOM,但执行成百上千次检查以查看自handleResize附加到mousemove事件以来是否没有任何变化仍然是昂贵
2021-04-13 23:46:13

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

const handleResize = () => {
  setActivePoint(activePoint => {
    console.log(activePoint);
    return activePoint;
  })
};
在使用了该ref方法(定义您自己的状态设置器函数也更新 ref)之后,我开始更喜欢这种方法,特别是在具有useEffect(或具有依赖项数组的类似钩子)的功能组件中主要是因为如果您将该ref方法与 any 一起使用useEffects,您最终不得不将您的 setter 函数传递到 中useEffect,我觉得这很烦人。
2021-03-19 23:46:13
有没有理由说明这不是公认答案的首选解决方案?非常简洁。还有为什么这行得通?它如何解决范围界定问题?
2021-03-26 23:46:13
不确定这是否是好的解决方案。setState 不应该运行任意代码。
2021-04-04 23:46:13
@aturc setState 支持功能更新:如果一个函数被传递给 setState 它接收当前状态作为第一个参数。该函数必须返回一个新状态。如果新状态与旧状态相同,则跳过渲染。所以它可以用来做副作用。reactjs.org/docs/hooks-reference.html#functional-updates
2021-04-12 23:46:13
  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
    });
  };

对于那些使用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时,它会自动更新引用和状态。

对敬畏 ChrisBrownie55 的建议只是一个小小的补充。

可以实现自定义钩子以避免重复此代码,并以与标准几乎相同的方式使用此解决方案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);
};
您知道这在性能(或其他任何方面)方面是否有任何缺点?
2021-04-04 23:46:13