即使我将其设置为oldvalue + 1,状态也不会更新。
注销ltrNewValue
或rtlNewValue
的值时,它总是相同的。就是因为它被初始状态所覆盖。
const Row = (props) => {
const [rowState, setRowState] = useState({
renderInterval: null,
value: 0,
});
useEffect(() => {
const interval = setInterval(counterIntervalFunction, props.speed);
setRowState({ ...rowState, renderInterval: interval });
}, []);
const counterIntervalFunction = () => {
if (props.isRunning && props.direction === 'ltr') {
const ltrNewValue = rowState.value === 2 ? 0 : rowState.value + 1;
console.log(ltrNewValue); // always 1
setRowState({ ...rowState, value: ltrNewValue });
console.log(rowState.value); // always 0
props.setRotatingValue(props.index, rowState.value);
} else if (props.isRunning && props.direction === 'rtl') {
const rtlNewValue = rowState.value === 0 ? 2 : rowState.value - 1;
setRowState({ ...rowState, value: rtlNewValue });
props.setRotatingValue(props.index, rowState.value);
} else {
clearCounterInterval();
}
};
我的最终目标是将rowState.value
递增到2
,然后在无限循环中将其设置为0
。如何正确执行此操作?
答案 0 :(得分:2)
我不确定,但看来您在这里过时的回调有问题。
useEffect(() => {
const interval = setInterval(counterIntervalFunction, props.speed);
setRowState({ ...rowState, renderInterval: interval });
}, []);
此效果仅运行一次-首次安装组件时。该间隔使用counterIntervalFunction
函数:
const counterIntervalFunction = () => {
if (props.isRunning && props.direction === 'ltr') {
const ltrNewValue = rowState.value === 2 ? 0 : rowState.value + 1;
console.log(ltrNewValue); // always 1
setRowState({ ...rowState, value: ltrNewValue });
console.log(rowState.value); // always 0
props.setRotatingValue(props.index, rowState.value);
} else if (props.isRunning && props.direction === 'rtl') {
const rtlNewValue = rowState.value === 0 ? 2 : rowState.value - 1;
setRowState({ ...rowState, value: rtlNewValue });
props.setRotatingValue(props.index, rowState.value);
} else {
clearCounterInterval();
}
};
counterIntervalFunction
捕获props
的引用,并使用它来确定向用户显示的内容。但是,因为该函数仅在安装组件时运行,因此该事件仅在将道具原先传递给该函数的情况下运行!您可以在this codesandbox.io
这就是为什么您应该将所有外部依赖项放入依赖项数组的原因:
useEffect(() => {
const interval = setInterval(counterIntervalFunction, props.speed);
setRowState({ ...rowState, renderInterval: interval });
}, [counterIntervalFunction, props.speed, rowState]);
但是,这将导致无限循环。
在useEffect
中设置状态通常被认为是一个坏主意,因为它会导致无限循环-更改状态将导致组件重新渲染,并引发其他效果等。
查看效果循环,您实际上感兴趣的是捕获对间隔的引用。如果更改此间隔,则实际上不会对组件产生任何影响,因此我们可以使用引用来跟踪它,而不是使用状态。引用不会重新呈现。这也意味着我们可以将value
更改为独立值。
因为我们现在不再依赖rowState
,所以我们可以从依赖关系数组中删除它,从而防止无限渲染。现在,我们的效果仅取决于props.speed
和counterIntervalFunction
:
const renderInterval = React.useRef();
const [value, setValue] = React.useState(0);
useEffect(() => {
renderInterval.current = setInterval(counterIntervalFunction, props.speed);
return () => {
cancelInterval(renderInterval.current);
};
}, [props.speed, counterIntervalFunction]);
这将起作用,但是由于counterIntervalFunction
是内联定义的,因此将在每个渲染中重新创建它,从而导致效果触发每个渲染。我们可以使用React.useCallback()
来稳定它。我们还将要添加此函数的所有依赖关系,以确保不捕获对道具的陈旧引用,并且可以将setRowState
更改为setValue
。最后,由于间隔已被useEffect
取消,因此我们不再需要调用clearCounterInterval
。
const counterIntervalFunction = React.useCallback(() => {
if (props.isRunning && props.direction === 'ltr') {
const ltrNewValue = value === 2 ? 0 : value + 1;
setValue(ltrNewValue);
props.setRotatingValue(props.index, ltrNewValue);
} else if (isRunning && props.direction === 'rtl') {
const rtlNewValue = value === 0 ? 2 : value - 1;
setValue(rtlNewValue);
props.setRotatingValue(props.index, rtlNewValue);
}
}, [value, props]);
可以通过将所需的props移至参数来进一步简化该操作:
const counterIntervalFunction = React.useCallback((isRunning, direction, setRotatingValue, index) => {
if (isRunning === false) {
return;
}
if (direction === 'ltr') {
const ltrNewValue = value === 2 ? 0 : value + 1;
setValue(ltrNewValue);
setRotatingValue(index, ltrNewValue);
} else if (props.direction === 'rtl') {
const rtlNewValue = value === 0 ? 2 : value - 1;
setValue(rtlNewValue);
setRotatingValue(index, rtlNewValue);
}
}, [value]);
如果不是setRotatingValue
,这可能甚至更简单:现在,您有一个组件既可以保持自己的状态 则可以通知父对象状态更改。您应该意识到,组件状态value
在调用时不一定会更新,但是setRotatingValue
绝对会更新。这可能导致 parent 看到的状态不同于孩子的状态。我建议您更改数据流的方式,以使父级拥有当前值,并通过道具传递该值,而不是子级。
这使我们完成以下代码:
function Row = (props) => {
const renderInterval = React.useRef();
const [value, setValue] = React.useState(0);
useEffect(() => {
renderInterval.current = setInterval(counterIntervalFunction, props.isRunning, props.direction, props.setRotatingValue, props.index);
return () => {
cancelInterval(renderInterval.current);
};
}, [props, counterIntervalFunction]);
const counterIntervalFunction = React.useCallback((isRunning, direction, setRotatingValue, index) => {
if (isRunning === false) {
return;
}
if (direction === 'ltr') {
const ltrNewValue = value === 2 ? 0 : value + 1;
setValue(ltrNewValue);
setRotatingValue(index, ltrNewValue);
} else if (props.direction === 'rtl') {
const rtlNewValue = value === 0 ? 2 : value - 1;
setValue(rtlNewValue);
setRotatingValue(index, rtlNewValue);
}
}, [value]);
...
}
在此代码中,您会注意到,每次道具或功能更改时,我们都会运行效果。不幸的是,这意味着效果将在每个循环中返回,因为我们需要保留对value
的新引用。除非您重构counterIntervalFunction
而不用setRotatingValue
或通知父对象,否则此组件将始终存在此问题,以使该函数不包含其自身的状态。解决此问题的另一种方法是使用setValue
的函数形式:
const counterIntervalFunction = React.useCallback((isRunning, direction, setRotatingValue, index) => {
if (isRunning === false) {
return;
}
setValue(value => {
if (direction === 'ltr') {
return value === 2 ? 0 : value + 1;
} else if (direction ==' rtl') {
return value === 0 ? 2 : value - 1;
}
})
}, []);
由于不能保证状态更新是同步运行的,因此无法从setValue
调用中提取值,然后再调用setRotatingValue
函数。 :(您可以在setRotatingValue
回调内潜在地调用setValue
,但这给了我希比吉卜赛人。
答案 1 :(得分:1)
这是一个时间间隔,当您直接依靠名称为rowState的旧状态直接调用setState时,可能会使事情变得混乱,请尝试以下操作:
setRowState(oldstate=> { ...rowState, value: oldstate.value+1 });