在 React.js 中创建范围滑块
Creat Range Slider in React.js
我正在尝试在 react.js
中创建范围滑块
rangeSlider.jsx
const RangeSlider = ({onChange}) => {
const [slider, setSlider] = useState({
max: 100,
min: 0,
value: 0,
label: ''
});
const onSlide = () => {
onChange(slider.value);
}
return (
<div className="range-slider">
<p>{slider.label}</p>
<input type="range" min={slider.min} max={slider.max} value={slider.value}
onChange={() => onSlide()} className="slider" id="myRange"></input>
</div>
);
}
export default RangeSlider;
然后我在其他组件中使用它
<RangeSlider onChange={(value) => sliderValueChanged(value)} />
- 如果我想传入自定义标签,我将如何更新状态
这样做?
- 我必须为此使用 React.memo 吗?我的理解是,每次滑块值更改时,都会创建一个新的滑块实例。
- 我希望这最终是健壮的(步骤、多手柄、工具提示等),任何帮助
不胜感激。
- 您可以使用
label
解构道具并使用 useEffect
更改当前滑块
const RangeSlider = ({ onChange, label }) => {
// other stuff
useEffect(() => {
setSlider(current => ({
...current,
label
}));
}, [label]);
};
RangerSlider.js
<RangeSlider label="Custom label" onChange={(value) => sliderValueChanged(value)} />
- 不一定,这取决于您如何处理依赖项数组以确保它只在必要时重新呈现。
编辑:
我还注意到您实际上并没有更改 RangeSlider
的本地状态以订阅更改。您可能还想在 RangerSlider
中添加另一个 useEffect
const RangeSlider = ({ value, onChange, label }) => {
// other stuff
useEffect(() => {
setSlider(current => ({
...current,
value
}));
}, [value]);
};
RangerSlider.js
<RangeSlider label="Custom label" onChange={(value) => sliderValueChanged(value)} value={INSERT_THE_STATE_HERE} />
如果我要实现这个组件,我会避免在 RangerSlider
中创建本地状态并将所有值和滑块配置作为 props
传递以提高性能
当你想创建一个可重用的组件时,总是尝试从它使用的地方传递配置,并将所有通用配置保留在组件内部
例如:
了解 useMemo
和 useReducer
的工作原理
const App = () => {
//Keep slider value in parent
const [parentVal, setParentVal] = useState(10);
//need useCallback why? if this component rendered we don't want to recreate the onChange function
const sliderValueChanged = useCallback(val => {
console.log("NEW VALUE", val);
setParentVal(val);
});
// need useMemo why? if this component rendered we don't want to recreate a new instance of the configuration object,
// but recreate it when parentVal gets changed, so Slider will re-render,
// and you can remove parentVal from dependency array and once the parent parentVal gets updated slider will not be re-renderd
const sliderProps = useMemo(
() => ({
min: 0,
max: 100,
value: parentVal,
step: 2,
label: "This is a reusable slider",
onChange: e => sliderValueChanged(e)
}),
// dependency array, this will call useMemo function only when parentVal gets changed,
// if you 100% confident parentVal only updated from Slider, then you can keep empty dependency array
// and it will not re-render for any configuration object change
[parentVal]
);
return (
<div>
<h1>PARENT VALUE: {parentVal}</h1>
<RangeSlider {...sliderProps} classes="additional-css-classes" />
</div>
);
};
并在 Slider 组件中
//destructive props
const RangeSlider = ({ classes, label, onChange, value, ...sliderProps }) => {
//set initial value to 0 this will change inside useEffect in first render also| or you can directly set useState(value)
const [sliderVal, setSliderVal] = useState(0);
// keep mouse state to determine whether i should call parent onChange or not.
// so basically after dragging the slider and then release the mouse then we will call the parent onChange, otherwise parent function will get call each and every change
const [mouseState, setMouseState] = useState(null);
useEffect(() => {
setSliderVal(value); // set new value when value gets changed, even when first render
}, [value]);
const changeCallback = (e) => {
setSliderVal(e.target.value); // update local state of the value when changing
}
useEffect(() => {
if (mouseState === "up") {
onChange(sliderVal)// when mouse is up then call the parent onChange
}
}, [mouseState])
return (
<div className="range-slider">
<p>{label}</p>
<h3>value: { sliderVal }</h3>
<input
type="range"
value={sliderVal}
{...sliderProps}
className={`slider ${classes}`}
id="myRange"
onChange={changeCallback}
onMouseDown={() => setMouseState("down")} // When mouse down set the mouseState to 'down'
onMouseUp={() => setMouseState("up")} // When mouse down set the mouseState to 'up' | now we can call the parent onChnage
/>
</div>
);
};
export default memo(RangeSlider);
查看我的demo
我想这个答案就是 3 个问题
使用parent中的配置传递不常见的配置如label
使用备忘录?是的,所以 Slider 组件只有在 props 发生变化时才会被渲染。但是你必须仔细设计它(例如:useMemo 和 useCallback)
steps
?在父级中使用配置对象来传递这些。
以防万一,如果您需要一种很好的方式来包装范围,我建议您使用自定义挂钩
const useSlider = ({ value, ...config }) => {
const [sliderVal, setSliderVal] = useState(value); // keep a state for each slider
const [configuration, setConfiguration] = useState(config); // keep the configuration for each slider
const onChange = useCallback(val => {
setSliderVal(val);
// useCallback why? we dont need to recreate every time this hook gets called
}, []);
useEffect(() => {
setConfiguration({
...config,
onChange,
value: sliderVal
});
// when sliderVal gets changed call this effect
// and return a new configuration, so the slider can rerender with latest configuration
}, [sliderVal]);
return [sliderVal, configuration];
};
这是一个demo
这可能会进一步改进
您必须问自己的第一个问题是:我在哪里保存滑块的状态?答:将状态保持在父组件中,并将其传递给 RangeSlider,以保持状态受控和一致。在大多数情况下,像这样的实用程序组件永远不应该保持自己的状态。
const ParentComponent = () => {
const [sliderProps, setSliderProps] = useState({
min: 0,
max: 100,
value: 20,
label: 'This is a reusable slider'
});
const [sliderValue, setSliderValue] = useState(0);
const handleSliderChange = e => {
setSliderValue(e.target.value);
};
const handleMouseUp = e => {
// do something with sliderValue
};
return (
<RangeSlider
{...sliderProps}
classes=""
onChange={handleSliderChange}
onMouseUp={handleMouseUp}
value={sliderValue} />
);
}
而您的 Range Slider 组件:
const RangeSlider = ({
classes,
label,
onChange,
onMouseUp,
value,
...sliderProps
}) => {
useEffect(() => {
// if you dont set your inital state in your parent component
// this effect will only run once when component did mount and
// passes the initial value back to the parent component.
onChange(value);
}, []);
return (
<div className="range-slider">
<p>{label}</p>
<h3>value: { value }</h3>
<input
{...sliderProps}
type="range"
value={value}
className={`slider ${classes}`}
id="myRange"
onChange={onChange}
onMouseUp={onMouseUp} // only if such effect is desired
/>
</div>
);
};
export default memo(RangeSlider);
关于您对调用新实例的担忧。当你的通行证道具发生变化时,它不会为这个组件产生一个新的实例。它只会导致重新渲染。在像这样的 RangeSlider 这样的小组件中,重新渲染它只需要很少的计算能力,所以没有必要绕过传递的 props 并从你的父级一致地传递 props。
通常范围滑块会直接影响您的 UI 或保持表单的状态,因此仅在“mouseup”上触发 onChange 会限制您的组件以实现可重用性目的并且仅涵盖极少数情况.
如果您想解释 @Kalhan.Toress 之类的行为,我建议您在父组件中处理该逻辑。要启用它,您只需如前所示通过回调传递“mouseup”事件。
在这种情况下,您真的不需要担心性能问题。你的 RangeSlider 太小太简单了,以至于搞砸了你的应用程序。
希望对您有所帮助
我正在尝试在 react.js
中创建范围滑块rangeSlider.jsx
const RangeSlider = ({onChange}) => {
const [slider, setSlider] = useState({
max: 100,
min: 0,
value: 0,
label: ''
});
const onSlide = () => {
onChange(slider.value);
}
return (
<div className="range-slider">
<p>{slider.label}</p>
<input type="range" min={slider.min} max={slider.max} value={slider.value}
onChange={() => onSlide()} className="slider" id="myRange"></input>
</div>
);
}
export default RangeSlider;
然后我在其他组件中使用它
<RangeSlider onChange={(value) => sliderValueChanged(value)} />
- 如果我想传入自定义标签,我将如何更新状态 这样做?
- 我必须为此使用 React.memo 吗?我的理解是,每次滑块值更改时,都会创建一个新的滑块实例。
- 我希望这最终是健壮的(步骤、多手柄、工具提示等),任何帮助 不胜感激。
- 您可以使用
label
解构道具并使用useEffect
更改当前滑块
const RangeSlider = ({ onChange, label }) => {
// other stuff
useEffect(() => {
setSlider(current => ({
...current,
label
}));
}, [label]);
};
RangerSlider.js
<RangeSlider label="Custom label" onChange={(value) => sliderValueChanged(value)} />
- 不一定,这取决于您如何处理依赖项数组以确保它只在必要时重新呈现。
编辑:
我还注意到您实际上并没有更改 RangeSlider
的本地状态以订阅更改。您可能还想在 RangerSlider
useEffect
const RangeSlider = ({ value, onChange, label }) => {
// other stuff
useEffect(() => {
setSlider(current => ({
...current,
value
}));
}, [value]);
};
RangerSlider.js
<RangeSlider label="Custom label" onChange={(value) => sliderValueChanged(value)} value={INSERT_THE_STATE_HERE} />
如果我要实现这个组件,我会避免在 RangerSlider
中创建本地状态并将所有值和滑块配置作为 props
传递以提高性能
当你想创建一个可重用的组件时,总是尝试从它使用的地方传递配置,并将所有通用配置保留在组件内部
例如:
了解 useMemo
和 useReducer
的工作原理
const App = () => {
//Keep slider value in parent
const [parentVal, setParentVal] = useState(10);
//need useCallback why? if this component rendered we don't want to recreate the onChange function
const sliderValueChanged = useCallback(val => {
console.log("NEW VALUE", val);
setParentVal(val);
});
// need useMemo why? if this component rendered we don't want to recreate a new instance of the configuration object,
// but recreate it when parentVal gets changed, so Slider will re-render,
// and you can remove parentVal from dependency array and once the parent parentVal gets updated slider will not be re-renderd
const sliderProps = useMemo(
() => ({
min: 0,
max: 100,
value: parentVal,
step: 2,
label: "This is a reusable slider",
onChange: e => sliderValueChanged(e)
}),
// dependency array, this will call useMemo function only when parentVal gets changed,
// if you 100% confident parentVal only updated from Slider, then you can keep empty dependency array
// and it will not re-render for any configuration object change
[parentVal]
);
return (
<div>
<h1>PARENT VALUE: {parentVal}</h1>
<RangeSlider {...sliderProps} classes="additional-css-classes" />
</div>
);
};
并在 Slider 组件中
//destructive props
const RangeSlider = ({ classes, label, onChange, value, ...sliderProps }) => {
//set initial value to 0 this will change inside useEffect in first render also| or you can directly set useState(value)
const [sliderVal, setSliderVal] = useState(0);
// keep mouse state to determine whether i should call parent onChange or not.
// so basically after dragging the slider and then release the mouse then we will call the parent onChange, otherwise parent function will get call each and every change
const [mouseState, setMouseState] = useState(null);
useEffect(() => {
setSliderVal(value); // set new value when value gets changed, even when first render
}, [value]);
const changeCallback = (e) => {
setSliderVal(e.target.value); // update local state of the value when changing
}
useEffect(() => {
if (mouseState === "up") {
onChange(sliderVal)// when mouse is up then call the parent onChange
}
}, [mouseState])
return (
<div className="range-slider">
<p>{label}</p>
<h3>value: { sliderVal }</h3>
<input
type="range"
value={sliderVal}
{...sliderProps}
className={`slider ${classes}`}
id="myRange"
onChange={changeCallback}
onMouseDown={() => setMouseState("down")} // When mouse down set the mouseState to 'down'
onMouseUp={() => setMouseState("up")} // When mouse down set the mouseState to 'up' | now we can call the parent onChnage
/>
</div>
);
};
export default memo(RangeSlider);
查看我的demo
我想这个答案就是 3 个问题
使用parent中的配置传递不常见的配置如
label
使用备忘录?是的,所以 Slider 组件只有在 props 发生变化时才会被渲染。但是你必须仔细设计它(例如:useMemo 和 useCallback)
steps
?在父级中使用配置对象来传递这些。
以防万一,如果您需要一种很好的方式来包装范围,我建议您使用自定义挂钩
const useSlider = ({ value, ...config }) => {
const [sliderVal, setSliderVal] = useState(value); // keep a state for each slider
const [configuration, setConfiguration] = useState(config); // keep the configuration for each slider
const onChange = useCallback(val => {
setSliderVal(val);
// useCallback why? we dont need to recreate every time this hook gets called
}, []);
useEffect(() => {
setConfiguration({
...config,
onChange,
value: sliderVal
});
// when sliderVal gets changed call this effect
// and return a new configuration, so the slider can rerender with latest configuration
}, [sliderVal]);
return [sliderVal, configuration];
};
这是一个demo
这可能会进一步改进
您必须问自己的第一个问题是:我在哪里保存滑块的状态?答:将状态保持在父组件中,并将其传递给 RangeSlider,以保持状态受控和一致。在大多数情况下,像这样的实用程序组件永远不应该保持自己的状态。
const ParentComponent = () => {
const [sliderProps, setSliderProps] = useState({
min: 0,
max: 100,
value: 20,
label: 'This is a reusable slider'
});
const [sliderValue, setSliderValue] = useState(0);
const handleSliderChange = e => {
setSliderValue(e.target.value);
};
const handleMouseUp = e => {
// do something with sliderValue
};
return (
<RangeSlider
{...sliderProps}
classes=""
onChange={handleSliderChange}
onMouseUp={handleMouseUp}
value={sliderValue} />
);
}
而您的 Range Slider 组件:
const RangeSlider = ({
classes,
label,
onChange,
onMouseUp,
value,
...sliderProps
}) => {
useEffect(() => {
// if you dont set your inital state in your parent component
// this effect will only run once when component did mount and
// passes the initial value back to the parent component.
onChange(value);
}, []);
return (
<div className="range-slider">
<p>{label}</p>
<h3>value: { value }</h3>
<input
{...sliderProps}
type="range"
value={value}
className={`slider ${classes}`}
id="myRange"
onChange={onChange}
onMouseUp={onMouseUp} // only if such effect is desired
/>
</div>
);
};
export default memo(RangeSlider);
关于您对调用新实例的担忧。当你的通行证道具发生变化时,它不会为这个组件产生一个新的实例。它只会导致重新渲染。在像这样的 RangeSlider 这样的小组件中,重新渲染它只需要很少的计算能力,所以没有必要绕过传递的 props 并从你的父级一致地传递 props。
通常范围滑块会直接影响您的 UI 或保持表单的状态,因此仅在“mouseup”上触发 onChange 会限制您的组件以实现可重用性目的并且仅涵盖极少数情况. 如果您想解释 @Kalhan.Toress 之类的行为,我建议您在父组件中处理该逻辑。要启用它,您只需如前所示通过回调传递“mouseup”事件。
在这种情况下,您真的不需要担心性能问题。你的 RangeSlider 太小太简单了,以至于搞砸了你的应用程序。
希望对您有所帮助