#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