Слайдер Creat Range в 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? Насколько я понимаю, каждый раз, когда значение ползунка изменяется, создается новый экземпляр ползунка.
  • Я хотел бы, чтобы это было надежным (шаги, мульти-ручки, подсказки и т. Д.), В конце концов, любая помощь приветствуется.

person Skeeter62889    schedule 04.07.2020    source источник


Ответы (3)


Если вы хотите создать повторно используемый компонент, всегда старайтесь передать конфигурацию из того места, где он используется, и сохраните все общие конфигурации в стороне от компонента.

EX: Прочитайте, как работают useMemo и useReducer

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);

посмотрите мою демонстрацию

Я предполагаю, что этот ответ вызывает 3 вопроса

  1. используйте конфигурацию в родительском элементе, чтобы передать нестандартную конфигурацию, например label

  2. Использовать памятку? Да, поэтому компонент Slider будет отображаться только при изменении свойств. Но вы должны тщательно его спроектировать (например, 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];
};

Вот демонстрация

Это может быть дальнейшее улучшение

person Kalhan.Toress    schedule 04.07.2020
comment
Хорошее объяснение! - person Vinodhan; 04.07.2020
comment
Приятно, это очень помогло. Итак, если я собираюсь использовать несколько ползунков диапазона на странице, нужно ли мне использовать useState, useCallback и useMemo для каждого экземпляра? Например, у меня есть 5 ползунков диапазона в моем родителе, и у каждого есть свои собственные useState, useCallback и useMemo. - person Skeeter62889; 05.07.2020
comment
Рад, что помог тебе! @ Skeeter62889 ДА, потому что у вас должен быть разный набор конфигураций для разных ползунков, не так ли? Так что лучше иметь отдельную логику, иначе будет сложно поддерживать, когда код вырастет - person Kalhan.Toress; 05.07.2020
comment
@ Skeeter62889 Я только что обновил ответ с помощью хорошего подхода, проверьте это, удачного кодирования .. - person Kalhan.Toress; 05.07.2020

  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, чтобы подписаться на изменения. Вы также можете добавить еще useEffect в свой RangerSlider

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 для повышения производительности.

person blankart    schedule 04.07.2020

Первый вопрос, который вы должны себе задать: где сохранить состояние слайдера? Ответ: Сохраните состояние в родительском компоненте и передайте его 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, требуется очень небольшая вычислительная мощность для его повторного рендеринга, поэтому нет необходимости взламывать проходящие реквизиты и просто последовательно передавать реквизиты от вашего родителя.

Обычно ползунки диапазона имеют прямое влияние на ваш пользовательский интерфейс или сохраняют состояние формы, поэтому включение onChange только при наведении курсора мыши ограничивает ваш компонент для целей повторного использования и охватывает лишь очень немногие случаи. Если вам нравится поведение, подобное объяснению @ Kalhan.Toress, я рекомендую обрабатывать эту логику в ваших родительских компонентах. Чтобы включить это, вам просто нужно передать событие mouseup через обратный вызов, как показано ранее.

В этом случае вам действительно не нужно беспокоиться о производительности. Ваш RangeSlider слишком маленький, чтобы испортить ваше приложение.

надеюсь, это поможет

person jpmarks    schedule 04.07.2020