我现在正在 React 中构建一个轮播。要滚动到我正在使用的单个幻灯片document.querySelector
如下所示:
useEffect(() => {
document.querySelector(`#slide-${activeSlide}`).scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}, [activeSlide]);
这是不好的做法吗?毕竟,我直接在这里访问 DOM?React 会以什么方式做到这一点?
编辑:全return
方法
return (
<>
<button onClick={() => setActiveSlide(moveLeft)}>PREV</button>
<Wrapper id="test">
{children.map((child, i) => {
return (
<Slide id={`slide-${i}`} key={`slide-${i}`}>
{child}
</Slide>
);
})}
</Wrapper>
<button onClick={() => setActiveSlide(moveRight)}>NEXT</button>
</>
);
我无法回答是否为此使用 refs 的"你应该"部分,除非你这样做,你不需要这些id
值,除非你将它们用于其他事情。
但这是您将如何:
-
使用
useRef(null)
创建引用。const activeSlideRef = useRef(null);
-
将其放在当前处于活动状态的
Slide
上<Slide ref={i === activeSlide ? activeSlideRef : null} ...>
-
在您的
useEffect
中,使用 ref 的current
属性useEffect(() => { if (activeSlideRef.current) { activeSlideRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); } }, [activeSlide]);
(我认为
activeSlide
是这种效果的合理依赖。你不能使用引用,引用本身不会改变...
现场示例,为了方便起见,我已将您的一些组件转换为div
:
const {useEffect, useRef, useState} = React;
function Deck({children}) {
const [activeSlide, setActiveSlide] = useState(0);
const activeSlideRef = useRef(null);
useEffect(() => {
if (activeSlideRef.current) {
activeSlideRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}
}, [activeSlide]);
const moveLeft = Math.max(0, activeSlide - 1);
const moveRight = Math.min(children.length - 1, activeSlide + 1);
return (
<React.Fragment>
<button onClick={() => setActiveSlide(moveLeft)}>PREV</button>
<div id="test">
{children.map((child, i) => {
const active = i === activeSlide;
return (
<div className={`slide ${active ? "active" : ""}`} ref={active ? activeSlideRef : null} id={`slide-${i}`} key={`slide-${i}`}>
{child}
</div>
);
})}
</div>
<button onClick={() => setActiveSlide(moveRight)}>NEXT</button>
</React.Fragment>
);
}
ReactDOM.render(
<Deck>
<div>slide 0 </div>
<div>slide 1 </div>
<div>slide 2 </div>
<div>slide 3 </div>
<div>slide 4 </div>
<div>slide 5 </div>
<div>slide 6 </div>
<div>slide 7 </div>
<div>slide 8 </div>
<div>slide 9 </div>
</Deck>,
document.getElementById("root")
);
.slide {
height: 4em;
vertical-align: middle;
text-align: center;
}
#test {
overflow: scroll;
max-height: 20em;
}
.active {
font-weight: bold;
color: blue;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
在评论中,您问:
您知道是否可以在此处禁用
useEffect
以进行第一次渲染吗?
为了保留每个组件的非状态信息,有趣的是,您使用useRef
.useRef
的文档指出,它不仅适用于 DOM 元素引用,也适用于每个组件的非状态数据。所以你可以有
const firstRenderRef = useRef(true);
然后在useEffect
回调中,选中firstRenderRef.current
&mndash;如果true
,将其设置为false
,否则进行滚动:
const {useEffect, useRef, useState} = React;
function Deck({children}) {
const [activeSlide, setActiveSlide] = useState(0);
const activeSlideRef = useRef(null);
// *** Use a ref with the initial value `true`
const firstRenderRef = useRef(true);
console.log("render");
useEffect(() => {
// *** After render, don't do anything, just remember we've seen the render
if (firstRenderRef.current) {
console.log("set false");
firstRenderRef.current = false;
} else if (activeSlideRef.current) {
console.log("scroll");
activeSlideRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}
}, [activeSlide]);
const moveLeft = Math.max(0, activeSlide - 1);
const moveRight = Math.min(children.length - 1, activeSlide + 1);
return (
<React.Fragment>
<button onClick={() => setActiveSlide(moveLeft)}>PREV</button>
<div id="test">
{children.map((child, i) => {
const active = i === activeSlide;
return (
<div className={`slide ${active ? "active" : ""}`} ref={active ? activeSlideRef : null} id={`slide-${i}`} key={`slide-${i}`}>
{child}
</div>
);
})}
</div>
<button onClick={() => setActiveSlide(moveRight)}>NEXT</button>
</React.Fragment>
);
}
ReactDOM.render(
<Deck>
<div>slide 0 </div>
<div>slide 1 </div>
<div>slide 2 </div>
<div>slide 3 </div>
<div>slide 4 </div>
<div>slide 5 </div>
<div>slide 6 </div>
<div>slide 7 </div>
<div>slide 8 </div>
<div>slide 9 </div>
</Deck>,
document.getElementById("root")
);
.slide {
height: 4em;
vertical-align: middle;
text-align: center;
}
#test {
overflow: scroll;
max-height: 10em;
}
.active {
font-weight: bold;
color: blue;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
作为一个思想实验,我写了一个钩子,使人体工程学更容易一些:
function useInstance(instance = {}) {
// assertion: instance && typeof instance === "object"
const ref = useRef(instance);
return ref.current;
}
用法:
const inst = useInstance({first: true});
在useEffect
中,如果inst.first
为真,则执行inst.first = false;
;否则,执行滚动。
住:
const {useEffect, useRef, useState} = React;
function useInstance(instance = {}) {
// assertion: instance && typeof instance === "object"
const ref = useRef(instance);
return ref.current;
}
function Deck({children}) {
const [activeSlide, setActiveSlide] = useState(0);
const activeSlideRef = useRef(null);
const inst = useInstance({first: true});
console.log("render");
useEffect(() => {
// *** After render, don't do anything, just remember we've seen the render
if (inst.first) {
console.log("set false");
inst.first = false;
} else if (activeSlideRef.current) {
console.log("scroll");
activeSlideRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}
}, [activeSlide]);
const moveLeft = Math.max(0, activeSlide - 1);
const moveRight = Math.min(children.length - 1, activeSlide + 1);
return (
<React.Fragment>
<button onClick={() => setActiveSlide(moveLeft)}>PREV</button>
<div id="test">
{children.map((child, i) => {
const active = i === activeSlide;
return (
<div className={`slide ${active ? "active" : ""}`} ref={active ? activeSlideRef : null} id={`slide-${i}`} key={`slide-${i}`}>
{child}
</div>
);
})}
</div>
<button onClick={() => setActiveSlide(moveRight)}>NEXT</button>
</React.Fragment>
);
}
ReactDOM.render(
<Deck>
<div>slide 0 </div>
<div>slide 1 </div>
<div>slide 2 </div>
<div>slide 3 </div>
<div>slide 4 </div>
<div>slide 5 </div>
<div>slide 6 </div>
<div>slide 7 </div>
<div>slide 8 </div>
<div>slide 9 </div>
</Deck>,
document.getElementById("root")
);
.slide {
height: 4em;
vertical-align: middle;
text-align: center;
}
#test {
overflow: scroll;
max-height: 10em;
}
.active {
font-weight: bold;
color: blue;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
添加到接受的答案并尝试回答问题的"应该"部分,使用 refs 进行 DOM 操作:
- refs 可以更轻松地在线性时间内唯一标识 + 选择相应的元素(与 id 相比,与需要扫描 DOM 以选择正确元素的 document.querySelector 相比,多个元素可以错误地具有相同的 + 值(
- 引用知道 React 组件的生命周期,因此 React 将确保在组件卸载时将 ref 更新为 null,并且更方便。
- refs 作为一个概念 + 语法是与平台无关的,所以你可以在 React Native 和浏览器中使用相同的理解,而查询选择器是浏览器的东西
- 对于没有 DOM 的 SSR,refs 仍然可用于定位反应元素
当然,使用查询选择器并没有错,如果你在 React 世界中使用它,它不会破坏你的功能,但最好使用框架提供的东西,因为它在大多数情况下有一些默认的好处。