React useEffect钩子引用了不正确的值

时间:2019-03-05 21:12:58

标签: javascript reactjs react-hooks

我正在为要在项目(noUiSlider)中使用的外部库创建组件。

目前,我将此作为我的Slider组件:

function Slider(props) {
  const { defaultMin, defaultMax, step } = props;
  const sliderElement = useRef();

  useEffect(() => {
    if (!sliderElement.current) {
      return;
    }

    noUiSlider.create(sliderElement.current, {
      connect: true,
      start: [defaultMin, defaultMax],
      step,
      range: {
        min: [defaultMin],
        max: [defaultMax],
      },
    });

    if (props.onUpdate) {
      sliderElement.current.noUiSlider.on('update', props.onUpdate);
    }

    if (props.onEnd) {
      sliderElement.current.noUiSlider.on('end', props.onEnd);
    }
  }, []);

  return <div ref={sliderElement} />;
}

在这里,我等待DOM元素可用,然后在该DOM元素上创建noUiSlider实例(我返回div)。我还在效果内附加了一些事件处理程序(onUpdate和onEnd)。

Slider组件的用法如下...

// custom hook that just sets state
const [min, max, setMinMax] = useSlider(10, 300);

const handleEnd = () => {
  // these are outdated :(
  console.log(min, max);
}

<Slider 
  defaultMin={10} 
  defaultMax={300} 
  step={10} 
  onEnd={handleEnd} 
  onUpdate={setMinMax} 
/>

我遇到的问题是在handleEnd内,当我想引用minmax时,它们是初始值,而不是我希望更新的值。

如何在同一范围内从我在useEffect内调用的函数中检索更新的值?

我创建了一个codesandbox示例来展示其实际效果。

1 个答案:

答案 0 :(得分:3)

主要问题来自您在useEffect组件中调用Slider的方式。您添加了[]作为第二个参数,这可以防止多次调用您的效果。因此,它仅在第一次渲染后被调用一次,然后再也不会被调用,因此它保留了旧的onEnd,并保留了过时的最小值和最大值。

来自React's doc

  

如果要运行效果并仅将其清理一次(在挂载和卸载时),则可以将空数组([])作为第二个参数传递。这告诉React,您的效果不依赖于道具或状态的任何值,因此它不需要重新运行。

以此代替Slider.js,它可以起作用:

import React, { useEffect, useRef } from 'react';
import noUiSlider from 'nouislider';

function Slider(props) {
  const { defaultMin, defaultMax, step } = props;
  const sliderElement = useRef();

  useEffect(() => {
    noUiSlider.create(sliderElement.current, {
      connect: true,
      start: [defaultMin, defaultMax],
      step,
      range: {
        min: [defaultMin],
        max: [defaultMax],
      },
    });

    if (props.onUpdate) {
      sliderElement.current.noUiSlider.on('update', props.onUpdate);
    }
  }, []);

  useEffect(
    () => {
      if (props.onEnd) {
        sliderElement.current.noUiSlider.on('end', props.onEnd);
      }
      return () => {
        sliderElement.current.noUiSlider.off('end');
      };
    },
    [props.onEnd],
  );

  return <div ref={sliderElement} />;
}

export default Slider;

这里有些话:

  • props.onChange从未在Slider组件中定义或使用。我丢掉了。
  • useEffect必须分为两个useEffect
    • 处理滑块创建并连接更新功能(因为它始终保持对setMinMax中的useSlider.js的有效引用,因此不需要更新)
    • 另一种更新只是在props.onEnd发生更改时结束事件(实际上是在每个渲染上,请参见index.js > handleEnd)。 别忘了清理事件监听器。