等待DOM上的useCallback效果



我从useEffect调用一个useCallback,它将使组件的高度发生变化。然后,为了结束效果,我想将组件滚动到特定的点。问题是滚动操作不起作用,因为它很快就会触发:DOM还没有更新。

如果我把它包装在setTimeout中,它确实可以工作(因为一旦DOM更新,它就会触发(,但它有点古怪。

有没有一种干净的方法可以在追求效果之前等待DOM渲染?

不工作:

const viewportElement = useRef()
cont [innerHeight, setInnerHeight] = useState(0)
refreshDOM = useCallback(() => {
setInnerHeight(500) // Will cause the DOM to refresh and the inner div to grow in height.
}, [setInnerHeight])
useLayoutEffect(() => {
refreshDOM() // DOM updates
viewportElement.current.scroll(0, 200) // Fires to soon: innerDiv is still too small 
}, [refreshDOM, someOtherStuffTriggeringTheEffect])
return (
<div ref={viewportElement} style={{height: 100}}>
<div style={{height: innerHeight}}></div>
</div>
)

作品:


// ...
useLayoutEffect(() => {
refreshDOM() // DOM updates
setTimeout(
() => viewportElement.current && viewportElement.current.scroll(0, 200),
0,
) // DOM was refreshed, scroll operation works
}, [refreshDOM, someOtherStuffTriggeringTheEffect])
// ...

一个可能的解决方案是添加另一个useEffect并传递给依赖项innerHeight。当innerHeight更新时,意味着您可以调用滚动:

useEffect(() => {
viewportElement.current.scroll(0, 200)
}, [innerHeight]);

别忘了从useLayoutEffect中删除滚动

只是Rukkiesman答案的补充

它不起作用的原因是原因,设置高度为async。这意味着在您调用viewportElement.current.scroll(0, 200)时高度尚未更新

将其封装在setTimeout中之所以有效,是因为您已将其添加到微任务队列中,该队列将在完成更新工作后解决。