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