React - 正确触发表示 react 组件状态更改事件的回调

React - Properly triggering callback that represents a state change event from react component

提问人:PSK 提问时间:4/8/2023 更新时间:4/9/2023 访问量:164

问:

在一个应用程序中,我正在构建一个自定义范围滑块。我有一个维护当前值的状态,还有一个道具,它暴露了在更改此内部状态时触发的回调。以下是我目前的做法:

const Slider = ({onChange}) => {
  const [value, setValue] = useState(0)
  // ...
  useEffect(() => onChange?.(value), [value, onChange])
  // ...
}

这里的问题是,由于是依赖项列表的一部分,因此在组件加载时触发。但是,如果我将其从列表中删除,则不会在更改回调的定义时触发。onChangeonChange

正确的方法是什么?

  1. 以上是正确的,订阅者应该考虑到即使实际上没有发生更改,它也会被调用。onChange
  2. 在设置值状态的同一函数中触发 - 但它可能发生在多个地方,我需要确保在所有需要的地方调用它。onChange
javascript reactjs react-hooks 回调 状态

评论

0赞 Laurence Summers 4/8/2023
为什么您使用而不是直接从滑块调用?useEffectonChange
0赞 PSK 4/8/2023
@LaurenceSummers我正在编写一个自定义滑块,处理来自触摸和鼠标的拖动,因此有很多地方可能会发生变化。这是必需的,因为我想支持步骤和方向等,这些步骤和方向等在 html 输入滑块上都非常挑剔。value
0赞 Laurence Summers 4/9/2023
我明白你,但我认为更好的方法是直接调用,这样它只在预期时运行。目前,您正在引入一个不必要的中间状态。请记住,可以是任何传入它的函数,不需要依赖于特定的 HTML 事件。因此,我将使用将滑块状态作为道具与 onChange 函数一起传递,并根据需要调用。onChangevalueonChangeSlider

答:

0赞 luis miguel castro martinez 4/8/2023 #1

更新的答案,这是一个从头开始的滑块,功能齐全。

import {useState} from "react";
const CustomSlider = ({min, max, value, onChange}) => {
    const [sliderValue, setSliderValue] = useState(value);
    const handleSliderChange = (event) => {
        const newValue = parseInt(event.target.value);
        setSliderValue(newValue);
        onChange(newValue);
    }
    return (
        <div>
            <input
                type="range"
                min={min}
                max={max}
                value={sliderValue}
                onChange={handleSliderChange}
            />
        </div>
    )
}

export default function App() {
    const [value, setValue] = useState(0)
    return (
        <div>
            <CustomSlider
                min={0}
                max={100}
                value={value}
                onChange={val => setValue(val)}
            />
            {value}
        </div>
    );
}

评论

0赞 PSK 4/8/2023
我正在编写我自己的 Slider 组件 - 而不是来自 UI 库。
0赞 luis miguel castro martinez 4/8/2023
那你能提供完整的代码吗?
0赞 Xu Chen 4/8/2023 #2

有两个关键点: 第一个,OnChange()在这里有两个不同的含义:一个是反馈值的变化,另一个是反馈onChange()本身的变化。这两者可能应该分开。 第二点是 useEffect() 被设计为对已经给出的参数执行行为,它的重点是“获取一个新值并做一些事情”,例如基于新的 url 数据进行查询。useEffect() 实际上是一种尝试根据某些特定关注点解耦代码的设计。如果不使用 useEffect() 处理业务逻辑,则应考虑“首次运行”重复代码的问题。但是 OnChange() 关注的是不同的东西。OnChange() 本质上与 setState() 高度绑定。正如你所说,OnChange() 应该只关心数据变化,这与 useEffect() 不同。因此,更好的设计可能不是使用 useEffect(),而是包装 setState(),并在这个包装函数中调用 OnChange()。如果每次获得您关心的值时都有事情要做,请使用 useEffect()。useEffect() 自带的 “prepare to end the side effect of the last value involved” 函数钩子可以处理每次值更改时要做的事情。

0赞 Artur Minin 4/8/2023 #3

为了使你的实现按你的预期工作,你需要做两件事:

  1. 防止在第一次渲染时不必要地调用回调,为此,您可以检查是否已挂载,并且仅在已挂载时调用,以下需要对内部组件进行更改:onChangeSlideronChangeSlideruseEffectSlider
const Slider = ({ onChange }) => {
  const [value, setValue] = useState(0)
  // ...
  const isMounted = useRef(false)
  useEffect(() => {
    if (isMounted.current === false) {
      isMounted.current = true
      return
    }
    onChange?.(value)

    return () => {
      isMounted.current = false
    }
  }, [value, onChange])
  // ...
}
  1. 确保使用 React 的钩子记住传递给组件的回调,它可以防止由于外部组件的重新渲染而导致依赖列表中的 callback 对内部组件的不必要调用。onChangeSlideruseCallbackuseEffectSlideronChange