在 React 中使用 document.querySelector?我应该使用 refs 吗?如何? [英] Using document.querySelector in React? Should I use refs instead? How?

查看:58
本文介绍了在 React 中使用 document.querySelector?我应该使用 refs 吗?如何?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我现在正在用 React 构建一个旋转木马.要滚动到单个幻灯片,我使用 document.querySelector 像这样:

useEffect(() => {document.querySelector(`#slide-${activeSlide}`).scrollIntoView({行为:平稳",块:'最近的',内联:'最近的'});}, [activeSlide]);

这是不好的做法吗?毕竟,我在这里直接访问 DOM?这样做的 React 方式是什么?

完整的return方法

返回 (<><button onClick={() =>setActiveSlide(moveLeft)}>PREV</button><包装器 id="测试">{children.map((child, i) => {返回 (<幻灯片id={`slide-${i}`} key={`slide-${i}`}>{孩子}</幻灯片>);})}</包装器><button onClick={() =>setActiveSlide(moveRight)}>NEXT</button></>);

解决方案

我无法回答是否要为此使用 refs 的你应该"部分,除非你这样做,你不需要那些 id 值,除非您将它们用于其他用途.

但您可以这样做:

  1. 使用useRef(null) 创建参考.

    const activeSlideRef = useRef(null);

  2. 把它放在当前处于活动状态的Slide

  3. 在您的 useEffect 中,使用引用的 current 属性

    useEffect(() => {如果(activeSlideRef.current){activeSlideRef.current.scrollIntoView({行为:平稳",块:'最近的',内联:'最近的'});}}, [activeSlide]);

    (我认为 activeSlide 是该效果的合理依赖.您不能使用 ref,ref 本身不会变化......)

实时示例,为了方便起见,我已将您的一些组件转换为 div:

const {useEffect, useRef, useState} = React;功能甲板({儿童}){const [activeSlide, setActiveSlide] = useState(0);const activeSlideRef = useRef(null);useEffect(() => {如果(activeSlideRef.current){activeSlideRef.current.scrollIntoView({行为:平稳",块:'最近的',内联:'最近的'});}}, [activeSlide]);const moveLeft = Math.max(0, activeSlide - 1);const moveRight = Math.min(children.length - 1, activeSlide + 1);返回 (<React.Fragment><button onClick={() =>setActiveSlide(moveLeft)}>PREV</button><div id="测试">{children.map((child, i) => {const active = i === activeSlide;返回 (<div className={`slide ${active ?活动":"}`} ref={active ?activeSlideRef : null} id={`slide-${i}`} key={`slide-${i}`}>{孩子}

);})}

<button onClick={() =>setActiveSlide(moveRight)}>NEXT</button></React.Fragment>);}ReactDOM.render(<甲板><div>幻灯片 0 </div><div>幻灯片 1 </div><div>幻灯片 2 </div><div>幻灯片 3 </div><div>幻灯片 4 </div><div>幻灯片 5 </div><div>幻灯片 6 </div><div>幻灯片 7 </div><div>幻灯片 8 </div><div>幻灯片 9 </div></甲板>,document.getElementById("root"));

.slide {高度:4em;垂直对齐:中间;文本对齐:居中;}#测试 {溢出:滚动;最大高度:20em;}.积极的 {字体粗细:粗体;颜色:蓝色;}

<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;功能甲板({儿童}){const [activeSlide, setActiveSlide] = useState(0);const activeSlideRef = useRef(null);//*** 使用初始值为 `true` 的 refconst firstRenderRef = useRef(true);console.log("渲染");useEffect(() => {//*** 渲染之后,什么都不做,记住我们已经看到了渲染如果(firstRenderRef.current){console.log("设置假");firstRenderRef.current = false;} else if (activeSlideRef.current) {console.log("滚动");activeSlideRef.current.scrollIntoView({行为:平稳",块:'最近的',内联:'最近的'});}}, [activeSlide]);const moveLeft = Math.max(0, activeSlide - 1);const moveRight = Math.min(children.length - 1, activeSlide + 1);返回 (<React.Fragment><button onClick={() =>setActiveSlide(moveLeft)}>PREV</button><div id="测试">{children.map((child, i) => {const active = i === activeSlide;返回 (<div className={`slide ${active ?活动":"}`} ref={active ?activeSlideRef : null} id={`slide-${i}`} key={`slide-${i}`}>{孩子}

);})}

<button onClick={() =>setActiveSlide(moveRight)}>NEXT</button></React.Fragment>);}ReactDOM.render(<甲板><div>幻灯片 0 </div><div>幻灯片 1 </div><div>幻灯片 2 </div><div>幻灯片 3 </div><div>幻灯片 4 </div><div>幻灯片 5 </div><div>幻灯片 6 </div><div>幻灯片 7 </div><div>幻灯片 8 </div><div>幻灯片 9 </div></甲板>,document.getElementById("root"));

.slide {高度:4em;垂直对齐:中间;文本对齐:居中;}#测试 {溢出:滚动;最大高度:10em;}.积极的 {字体粗细:粗体;颜色:蓝色;}

<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 = {}) {//断言:实例 &&typeof instance === "object"const ref = useRef(instance);返回 ref.current;}

用法:

const inst = useInstance({first: true});

useEffect中,如果inst.first为true,则执行inst.first = false;;否则,进行滚动.

直播:

const {useEffect, useRef, useState} = React;函数使用实例(实例 = {}){//断言:实例 &&typeof instance === "object"const ref = useRef(instance);返回 ref.current;}功能甲板({儿童}){const [activeSlide, setActiveSlide] = useState(0);const activeSlideRef = useRef(null);const inst = useInstance({first: true});console.log("渲染");useEffect(() => {//*** 渲染之后,什么都不做,记住我们已经看到了渲染如果(inst.first){console.log("设置假");inst.first = 假;} else if (activeSlideRef.current) {console.log("滚动");activeSlideRef.current.scrollIntoView({行为:平稳",块:'最近的',内联:'最近的'});}}, [activeSlide]);const moveLeft = Math.max(0, activeSlide - 1);const moveRight = Math.min(children.length - 1, activeSlide + 1);返回 (<React.Fragment><button onClick={() =>setActiveSlide(moveLeft)}>PREV</button><div id="测试">{children.map((child, i) => {const active = i === activeSlide;返回 (<div className={`slide ${active ?活动":"}`} ref={active ?activeSlideRef : null} id={`slide-${i}`} key={`slide-${i}`}>{孩子}

);})}

<button onClick={() =>setActiveSlide(moveRight)}>NEXT</button></React.Fragment>);}ReactDOM.render(<甲板><div>幻灯片 0 </div><div>幻灯片 1 </div><div>幻灯片 2 </div><div>幻灯片 3 </div><div>幻灯片 4 </div><div>幻灯片 5 </div><div>幻灯片 6 </div><div>幻灯片 7 </div><div>幻灯片 8 </div><div>幻灯片 9 </div></甲板>,document.getElementById("root"));

.slide {高度:4em;垂直对齐:中间;文本对齐:居中;}#测试 {溢出:滚动;最大高度:10em;}.积极的 {字体粗细:粗体;颜色:蓝色;}

<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>

I am building a carousel right now, in React. To scroll to the individual slides I am using document.querySelector like so :

useEffect(() => {
    document.querySelector(`#slide-${activeSlide}`).scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'nearest'
    });
  }, [activeSlide]);

Is this bad practice? After all, I am accessing the DOM directly here? What would be the React way of doing this?

edit: full return method

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>
    </>
  );

解决方案

I can't answer the "should you" part of whether to use refs for this instead other than if you do, you don't need those id values unless you use them for something else.

But here's how you would:

  1. Use useRef(null) to create the ref.

    const activeSlideRef = useRef(null);
    

  2. Put it on the Slide that's currently active

    <Slide ref={i === activeSlide ? activeSlideRef : null} ...>
    

  3. In your useEffect, use the ref's current property

    useEffect(() => {
        if (activeSlideRef.current) {
            activeSlideRef.current.scrollIntoView({
              behavior: 'smooth',
              block: 'nearest',
              inline: 'nearest'
            });
        }
    }, [activeSlide]);
    

    (I think activeSlide is a reasonable dependency for that effect. You can't use the ref, the ref itself doesn't vary...)

Live example, I've turned some of your components into divs for convenience:

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>


In a comment you've asked:

Do you know whether it's possible to disable useEffect here for the first render?

To keep non-state per-component info around, interestingly you use useRef. The docs for useRef point out that it's not just for DOM element references, it's also for per-component non-state data. So you could have

const firstRenderRef = useRef(true);

then in your useEffect callback, check firstRenderRef.current &mndash; if it's true, set it false, otherwise do the scrolling:

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>


As a thought experiment, I wrote a hook to make the ergonomics a bit easier:

function useInstance(instance = {}) {
    // assertion: instance && typeof instance === "object"
    const ref = useRef(instance);
    return ref.current;
}

Usage:

const inst = useInstance({first: true});

In useEffect, if inst.first is true, do inst.first = false;; otherwise, do the scrolling.

Live:

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>

这篇关于在 React 中使用 document.querySelector?我应该使用 refs 吗?如何?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆