在 React 中使用 document.querySelector?我应该改用 refs 吗?如何?
Using document.querySelector in React? Should I use refs instead? How?
我现在正在用 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 的 "should you" 部分,除非你这样做,否则你不需要那些 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
是该效果的合理依赖项。您不能使用 ref,ref 本身不会变化...)
实例,为方便起见,我已将您的一些组件转换为 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>
在您提出的评论中:
Do you know whether it's possible to disable useEffect
here for the first render?
为了保留每个组件的非状态信息,有趣的是您使用 useRef
。 docs for 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>
添加到已接受的答案并尝试回答问题的 'should' 部分,使用 refs 进行 DOM 操作:
- refs 可以更容易地在线性时间内唯一地识别 + select 对应的元素(与 id 相比,多个元素可能错误地具有与 document.querySelector 相同的 + 值这需要扫描 DOM 到 select 正确的元素)
- refs 知道 React 组件的生命周期,因此 React 将确保在组件卸载时将 refs 更新为 null,并提供更多开箱即用的便利。
- refs 作为概念 + 语法与平台无关,因此您可以在 React Native 和浏览器中使用相同的理解,而查询 selector 是浏览器的东西
- 对于没有DOM的SSR,refs仍然可以用来定位react元素
当然,使用查询 selector 并没有错,如果您通常在 React 世界中使用它,它不会破坏您的功能,但最好使用框架提供的东西在大多数情况下有一些默认的好处。
我现在正在用 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 的 "should you" 部分,除非你这样做,否则你不需要那些 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
是该效果的合理依赖项。您不能使用 ref,ref 本身不会变化...)
实例,为方便起见,我已将您的一些组件转换为 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>
在您提出的评论中:
Do you know whether it's possible to disable
useEffect
here for the first render?
为了保留每个组件的非状态信息,有趣的是您使用 useRef
。 docs for 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>
添加到已接受的答案并尝试回答问题的 'should' 部分,使用 refs 进行 DOM 操作:
- refs 可以更容易地在线性时间内唯一地识别 + select 对应的元素(与 id 相比,多个元素可能错误地具有与 document.querySelector 相同的 + 值这需要扫描 DOM 到 select 正确的元素)
- refs 知道 React 组件的生命周期,因此 React 将确保在组件卸载时将 refs 更新为 null,并提供更多开箱即用的便利。
- refs 作为概念 + 语法与平台无关,因此您可以在 React Native 和浏览器中使用相同的理解,而查询 selector 是浏览器的东西
- 对于没有DOM的SSR,refs仍然可以用来定位react元素
当然,使用查询 selector 并没有错,如果您通常在 React 世界中使用它,它不会破坏您的功能,但最好使用框架提供的东西在大多数情况下有一些默认的好处。