TL;DR:转到 react 代码沙箱 并尝试单击任一按钮几次。 Buggy 没有到达幻灯片的末尾。
我正在制作一个可水平滚动的幻灯片。
它由一个带有overflow: auto
的
容器组成,其中包含水平溢出的轨道。每个slide都是track的子级,并且与视口一样宽。 container 有一个索引状态,表示哪个 slide 位于视图中:
const slides = [/* some array of json data */]
const [index, setIndex] = useState(0)
const track = useRef(null)
const container = useRef(null)
return (
<div ref={container} onScroll={onScroll}>
<div ref={track}>
{slides.map(() => (
<div></div>
))}
</div>
</div>
)
为了跟踪当前视图中的幻灯片,有一个
onScroll
功能:
const onScroll = () => {
const newIndex = // math to get index from DOM
if (index !== newIndex)
setIndex(newIndex)
}
还有一个
onClick
功能,允许用户自动滚动到下一张幻灯片:
const onClick = () => {
track.current.children[index + 1].scrollIntoView({
behavior: "smooth"
})
}
这个
onClick
被传递给 button 组件:
const BuggyButton = ({ disabled, onClick }) => (
<button type="button" disabled={disabled} onClick={onClick}>
click me
</button>
)
当用户到达最后一张幻灯片时,该按钮将被禁用:
<BuggyButton onClick={onClick} disabled={index === slides.length - 1} />
问题: 一旦
<BuggyButton/>
被禁用,scrollIntoView
就会(过早)停止。 这是为什么?
这是在代码沙箱中完全重现和简化的问题:https://codesandbox.io/s/disable-button-cancels-scroll-270hz?file=/src/App.js
这是我在 vanilla JS 中尽可能重现问题的方法:https://codesandbox.io/s/disable-button-cancels-scroll-vanilla-018mb
如果您只是在寻找解决方案(而不是了解正在发生的情况,那么代码沙箱中有一个
<ProperButton/>
组件可以正常工作。带着这个问题,我试图了解 react 在幕后的作用 这会导致浏览器中断滚动,但仍保持其位置而不将其重置为零。
总而言之,滚动停止是因为状态发生变化并且组件重新渲染了
if (index !== newIndex) {
setIndex(newIndex);
}
这与当组件位于闭包内和不在闭包内时 React 如何处理协调有关,这就是为什么当您将按钮放在应用程序函数内部和外部时它的行为不同的原因。
您可以在这里阅读更多相关信息
https://reactjs.org/docs/reconciliation.html
https://overreacted.io/how-are-function-components- Different-from-classes/
https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependency-change-too-often
此错误仅存在于 Chrome 中,这是因为 Chrome 处理滚动/聚焦的方式。
有两件事需要了解:
这就是正在发生的事情:
disabled
- 按钮不再具有焦点更多信息:https://github.com/facebook/react/issues/20770
如果您正在寻找更简单的解决方法,您可以在触发滚动之前
调用按钮上的
.blur()
:
const onClick = (event) => {
if (index === slides.length - 2) {
event.target.blur();
}
track.current.children[(index + 1) % slides.length].scrollIntoView({
behavior: "smooth",
});
};
这样,按钮将在滚动发生之前被禁用,因此 Chrome 不会检测到焦点的变化,也不会中断滚动。