Зачем использовать предыдущие возвраты, не определенные на первых крючках рендеринга?

#reactjs #api #react-hooks #use-ref

Вопрос:

Я хочу сохранить предыдущее значение в переменной и использовать его в функции. Допустим, если текущее значение равно 9, то предыдущее значение должно быть 8 (например, на одно меньше). Проблема в том, что console.log(prevServings) возвращает неопределенное значение при первом рендеринге и показывает предыдущее значение при втором рендеринге, но разница между текущими и предыдущими значениями равна 2 вместо 1. Я понимаю, что текущее значение недоступно при первом рендеринге, поэтому предыдущее значение не определено, но я не знаю, как это исправить. Любая помощь будет признательна. Заранее спасибо.

 const Child = ({originalData}) =>{
  //clone original data object with useState
  const [copyData, setCopyDta] = useState({});
  //clone the copied data 
  let duplicate = {...copyRecipe};
  
  
  //store previous servings value into a variable
  const usePrevious = (servings) => {
    const ref = useRef();
    useEffect(() => {
      ref.current = servings;
    }, [servings]);

    return ref.current;
  };
  const prevServings = usePrevious(duplicate.servings);
  
  //increase the number of servings on click
  const incrementHandle = () => {
    duplicate.servings = `${parseInt(duplicate.servings)   1}`;
    //this return undefined on the first render
    console.log(prevServings);
    setCopyRecipe(duplicate);
  }

  return(
    <p>{copyData.servings}</p>
    <Increment onClick={incrementHandle}/>
  )
} 

Ответ №1:

Он возвращается undefined , потому useEffect() что не сработает, по крайней мере, до первого рендеринга. Вы, вероятно, хотите сделать это вместо этого:

 const usePrevious = (servings) => {
  const ref = useRef(servings);
  useEffect(() => {
    ref.current = servings;
  }, [servings])
  return ref.current;
}
 

Хотя мне кажется, что об этом было бы трудно рассуждать. Я бы, вероятно, рекомендовал вместо этого использовать редуктор или обычное состояние. Ссылка полезна, если вы не хотите, чтобы компонент «реагировал» на изменения этого конкретного значения, но каждый раз, когда ссылка изменяется здесь, вы все равно запускаете обновление состояния.

Комментарии:

1. Когда я устанавливаю значение по умолчанию в качестве переданного значения, оно всегда возвращает текущее значение, когда я использую его вместо предыдущего значения.

Ответ №2:

Вопрос

Упс, не совсем дубликат. Проблема здесь в том, что, поскольку вы объявили usePrevious внутри компонента, он воссоздается каждый цикл рендеринга, эффективно сводя на нет любые усилия по кэшированию.

Решение

Переместите usePrevious крючок за пределы тела компонента, чтобы он был стабильной ссылкой. Возможно, вам также захочется удалить useEffect зависимость, чтобы кэшировать значение в каждом цикле рендеринга.

 //store previous servings value into a variable
const usePrevious = (value) => {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

const Child = ({originalData}) =>{
  //clone original data object with useState
  const [copyData, setCopyDta] = useState({});
  //clone the copied data 
  let duplicate = { ...copyData };
  
  const prevServings = usePrevious(duplicate.servings);
  
  //increase the number of servings on click
  const incrementHandle = () => {
    duplicate.servings = `${parseInt(duplicate.servings)   1}`;
    setCopyDta(duplicate);
  }

  return(
    <p>{copyData.servings}</p>
    <Increment onClick={incrementHandle}/>
  )
}
 

к вашему сведению

Просто к вашему сведению, let duplicate = { ...copyRecipe }; это всего лишь поверхностная копия, а не клон объекта, все вложенные свойства по-прежнему будут ссылками на объекты в исходном объекте. Может быть, это все, что вам нужно, но я просто хотел отметить, что это не настоящий клон объекта.

QnA

Проблема в том, что console.log(предварительные службы) возвращает неопределенное значение при первом рендеринге и показывает предыдущее значение при втором рендеринге

Я бы сказал, что это должно быть ожидаемым поведением, потому что при первоначальном рендеринге не было предыдущего рендеринга, из которого можно было бы кэшировать значение.

но разница между текущими и предыдущими значениями составляет 2 вместо 1.

Что касается проблемы «выключено на 2», из того, что я могу сказать, вы кэшируете не обновленную мелкую копию duplicate каждого рендеринга, и при incrementHandle нажатии вы регистрируете prevServings значение, а затем ставите в очередь обновление, которое запускает повторную отправку. Значение, которое вы регистрируете, и результат обновления состояния (т. Е. <p>{copyData.servings}</p> ) находятся на расстоянии двух циклов визуализации. Если вы сравните оба значения в одной и той же точке цикла рендеринга, вы увидите, что они находятся на расстоянии 1.

 useEffect(() => {
  console.log({ prevServings, current: copyData.servings })
})
 

Вывод журнала:

 {prevServings: undefined, current: 0}
{prevServings: 0, current: "1"}
{prevServings: "1", current: "2"}
{prevServings: "2", current: "3"}
{prevServings: "3", current: "4"}
 

Комментарии:

1. Спасибо вам за подробный ответ. Я последовал вашему предложению, создав отдельный компонент под названием usePrevious и импортировав его обратно в дочерний компонент. Но при первом щелчке он все равно возвращается неопределенным, и мне нужно, чтобы это было число.

2. Кроме того, спасибо за дополнительную информацию, я поискал ее в Интернете и нашел способ глубокого клонирования объекта, например {const cloneFood = JSON.parse(JSON.stringify(еда));}

3. @Simon Если нет предыдущего рендеринга, то каким будет предыдущее значение? Когда вы говорите, что вам нужно, чтобы это было число, вы имеете в виду тип или вам просто нужно определенное значение? Можете ли вы объяснить случай использования, когда вам нужно определенное предыдущее значение при первоначальном рендеринге?

4. Я работаю над сайтом рецептов, поэтому я хочу, чтобы количество ингредиентов соответствовало количеству порций. Я думаю об уравнении типа: newIngredientAmount = количество ингредиентов/Предварительные услуги * Текущие услуги. Таким образом, по умолчанию консервы должны быть значением рецепта по умолчанию. Например: значение по умолчанию равно 8, и когда вы нажимаете кнопку, она поворачивается на 9, поэтому предварительные сохранения равны 8, а текущие-9. Я надеюсь, что для вас это имеет больше смысла.

5.Вот мой фактический код: dropbox.com/s/qn0dan0m61593gd/1.png?dl=0 dropbox.com/s/cfvlmsei3gdyxkl/2.png?dl=0