在一个应用程序中,我正在构建一个自定义范围滑块。我有一个保持当前值的状态,还有一个暴露在此内部状态更改时触发的回调的道具。以下是我目前的做法:
const Slider = ({onChange}) => {
const [value, setValue] = useState(0)
// ...
useEffect(() => onChange?.(value), [value, onChange])
// ...
}
这里的问题是
onChange
是在组件加载时触发的,因为 onChange
是依赖列表的一部分。但是如果我从列表中删除它,它不会触发更改回调的定义。
这里正确的做法是什么?
onChange
的订阅者应该考虑到即使实际上没有发生变化也会调用它。onChange
在设置值状态的同一个函数中触发 - 但它可能发生在多个地方,我需要确保它在所有需要的地方被调用。更新的答案,这是一个从头开始功能齐全的滑块。
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>
);
}
关键有两点: 第一个,OnChange()在这里有两种不同的含义:一种是反馈值的变化,一种是反馈onChange()本身的变化。两者可能应该分开。 第二点,useEffect()的设计目的是针对一个已经给定的参数执行行为,它的重点是“获取一个新的值并做一些事情”,比如根据一个新的url数据进行查询。 useEffect() 实际上是一种基于某些特定关注点试图解耦代码的设计。如果不使用useEffect()处理业务逻辑,就要考虑“首次运行”的重复代码问题。但是 OnChange() 专注于不同的事情。 OnChange() 本质上高度绑定到 setState()。正如您所说,OnChange() 应该只关心数据变化,这与 useEffect() 不同。因此,更好的设计可能是不使用useEffect(),而是将setState()包装起来,并在这个包装好的函数中调用OnChange()。如果每次获得您关心的值时您都有事情要做,请使用 useEffect()。 useEffect()自带的“准备结束关注的最后一个值的副作用”函数钩子,可以处理每次值变化时要做的事情。
为了让您的实施工作如您所愿,您需要做两件事:
onChange
回调,为此您可以检查 Slider
是否已安装,并且仅当 onChange
已安装时才调用 Slider
,遵循 useEffect
所需的更改
里面Slider
成分: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])
// ...
}
onChange
组件的 Slider
回调是使用 React 的 useCallback
钩子记忆的,它可以防止在 useEffect
组件内不必要地调用 Slider
组件,其中 onChange
由于重新渲染而在依赖列表中回调外部组件。