【问题标题】:Creat Range Slider in React.js在 React.js 中创建范围滑块
【发布时间】:2020-10-24 18:31:45
【问题描述】:

我正在尝试在 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 吗?我的理解是,每次滑块值更改时,都会创建一个新的滑块实例。
  • 我希望它最终变得强大(步骤、多手柄、工具提示等),任何帮助 不胜感激。

【问题讨论】:

    标签: reactjs react-hooks rangeslider


    【解决方案1】:

    当您想创建可重用组件时,请始终尝试从使用它的位置传递配置,并将所有常用配置保留在组件内部

    前: 了解 useMemouseReducer 的工作原理

    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 个问题

    1. 在parent中使用配置传递label等不常见的配置

    2. 使用备忘录?是的,所以 Slider 组件只有在 props 改变时才会被渲染。但是你必须仔细设计它(例如:useMemo 和 useCallback)

    3. 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

    这可能会进一步改进

    【讨论】:

    • 很好的解释!
    • 很好,这很有帮助。那么,如果我要在一个页面上使用多个范围滑块,我是否需要为每个实例使用 useState、useCallback 和 useMemo?例如,我的父级中有 5 个范围滑块,每个滑块都有自己的 useState、useCallback 和 useMemo。
    • 很高兴它对您有所帮助! @Skeeter62889 是的,因为你必须为不同的滑块设置不同的配置不是吗?所以最好有单独的逻辑,否则代码长大后很难维护
    • @Skeeter62889 我刚刚用一个不错的方法更新了答案,检查一下,快乐编码..
    【解决方案2】:

    您必须问自己的第一个问题是:我在哪里保存滑块的状态?答:将状态保留在父组件中并将其传递给 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 这样的小组件中,重新渲染它只需要很少的计算能力,因此没有必要绕过传递的道具,只需从你的父母那里持续传递道具。

    通常范围滑块会直接影响您的 UI 或保持表单的状态,因此仅在“mouseup”上触发 onChange 将限制您的组件的可重用性,并且仅涵盖极少数情况。 如果您喜欢 @Kalhan.Toress 解释的行为,我建议您在父组件中处理该逻辑。要启用它,您只需通过回调传递“mouseup”事件,如前所示。

    在这种情况下,您真的不需要担心性能。你的 RangeSlider 太小太简单了,会弄乱你的应用程序。

    希望对你有帮助

    【讨论】:

      【解决方案3】:
      1. 您可以使用label 解构您的道具,并使用useEffect 更改您当前的滑块
      const RangeSlider = ({ onChange, label }) => {
        // other stuff
        useEffect(() => {
          setSlider(current => ({
            ...current,
            label
          }));
        }, [label]);
      };
      

      RangerSlider.js

       <RangeSlider label="Custom label" onChange={(value) => sliderValueChanged(value)} />
      
      1. 不一定,这取决于您如何处理依赖项数组以确保它仅在必要时重新呈现。

      编辑: 我还注意到,您实际上并没有更改 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 传递以提高性能

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-09-18
        • 1970-01-01
        • 2018-11-02
        • 1970-01-01
        • 1970-01-01
        • 2014-07-31
        相关资源
        最近更新 更多