javascript 如何在Map中使用react useRef来目标DOM

j1dl9f46  于 2023-04-28  发布在  Java
关注(0)|答案(8)|浏览(215)

我正在寻找一个解决方案,用react useRef()钩子获取DOM元素数组。
例如:

const Component = () => 
{

  // In `items`, I would like to get an array of DOM element
  let items = useRef(null);

  return <ul>
    {['left', 'right'].map((el, i) =>
      <li key={i} ref={items} children={el} />
    )}
  </ul>
}

我如何才能做到这一点?

qvk1mo1f

qvk1mo1f1#

useRef只是部分类似于React的ref(只是对象的结构,只有current字段)。
useRef钩子的目标是在渲染之间存储一些数据,并且更改这些数据不会触发重新渲染(不像useState那样)。
也只是温柔的提醒:最好避免在循环或if中初始化钩子。这是钩子的第一条规则
考虑到这一点,我们:
1.创建数组并在渲染之间保存它
1.我们通过createRef()初始化每个数组的元素
1.我们可以使用.current表示法来引用列表

const Component = () => {

  let refs = useRef([React.createRef(), React.createRef()]);

  useEffect(() => {
    refs.current[0].current.focus()
  }, []);

  return (<ul>
    {['left', 'right'].map((el, i) =>
      <li key={i}><input ref={refs.current[i]} value={el} /></li>
    )}
  </ul>)
}

这样我们就可以安全地修改数组(比如通过改变它的长度)。但是不要忘记,改变useRef存储的数据不会触发重新渲染。因此,为了改变长度以重新渲染,我们需要涉及useState

const Component = () => {

  const [length, setLength] = useState(2);
  const refs = useRef([React.createRef(), React.createRef()]);

  function updateLength({ target: { value }}) {
    setLength(value);
    refs.current = refs.current.splice(0, value);
    for(let i = 0; i< value; i++) {
      refs.current[i] = refs.current[i] || React.createRef();
    }
    refs.current = refs.current.map((item) => item || React.createRef());
  }

  useEffect(() => {
   refs.current[refs.current.length - 1].current.focus()
  }, [length]);

  return (<>
    <ul>
    {refs.current.map((el, i) =>
      <li key={i}><input ref={refs.current[i]} value={i} /></li>
    )}
  </ul>
  <input value={refs.current.length} type="number" onChange={updateLength} />
  </>)
}

另外,不要在第一次渲染时尝试访问refs.current[0].current-这将引发错误。

return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

所以你要么把它当作

return (<ul>
        {['left', 'right'].map((el, i) =>
          <li key={i}>
            <input ref={refs.current[i]} value={el} />
            {refs.current[i].current && refs.current[i].current.value}</li> // cannot read property `value` of undefined
        )}
      </ul>)

或者在useEffect钩子中访问它。原因:ref s是在元素渲染之后绑定的,所以在渲染期间第一次运行,它还没有初始化。

edqdpe6u

edqdpe6u2#

我将对skyboyer's answer进行一点扩展。为了优化性能(并避免潜在的奇怪bug),您可能更喜欢使用useMemo而不是useRef。因为useMemo接受回调函数作为参数而不是值,所以React.createRef只会在第一次渲染后初始化一次。在回调函数中,您可以返回一个createRef值的数组,并适当地使用该数组。

初始化:

const refs= useMemo(
    () => Array.from({ length: 3 }).map(() => createRef()),
    []
  );

这里的空数组(作为第二个参数)告诉React只初始化一次refs。如果ref count改变了,您可能需要将[x.length]作为“一个deps数组”传递,并动态创建ref:Array.from({ length: x.length }).map(() => createRef()),

用法:

refs[i+1 % 3].current.focus();
ahy6op9u

ahy6op9u3#

获取父引用并操纵子引用

const Component = () => {
  const ulRef = useRef(null);

  useEffect(() => {
    ulRef.current.children[0].focus();
  }, []);

  return (
    <ul ref={ulRef}>
      {['left', 'right'].map((el, i) => (
        <li key={i}>
          <input value={el} />
        </li>
      ))}
    </ul>
  );
};

我这样做,我认为这比其他建议的答案更简单。

toiithl6

toiithl64#

你可以将每个Map项分离到组件,而不是使用引用数组或类似的东西。当你把它们分开时,你可以独立使用useRef s:

const DATA = [
  { id: 0, name: "John" },
  { id: 1, name: "Doe" }
];

//using array of refs or something like that:
function Component() {
  const items = useRef(Array(DATA.length).fill(createRef()));
  return (
    <ul>
      {DATA.map((item, i) => (
        <li key={item.id} ref={items[i]}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

//seperate each map item to component:
function Component() {
  return (
    <ul>
      {DATA.map((item, i) => (
        <MapItemComponent key={item.id} data={item}/>
      ))}
    </ul>
  );
}

function MapItemComponent({data}){
  const itemRef = useRef();
  return <li ref={itemRef}>
    {data.name}
  </li>
}
gxwragnw

gxwragnw5#

如果你提前知道数组的长度,你可以简单地创建一个refs数组,然后通过它们的索引分配每个refs:

const Component = () => {
  const items = Array.from({length: 2}, a => useRef(null));
  return (
    <ul>
      {['left', 'right'].map((el, i)) => (
        <li key={el} ref={items[i]}>{el}</li>
      )}
    </ul>
  )
}
hl0ma9xz

hl0ma9xz6#

我遇到了这样的问题,读了Joer的答案,意识到你可以循环使用索引动态设置querySelector类,只设置一个引用到整个父类。很抱歉代码的负载,但希望这对某人有所帮助:

import React, { useRef, useState } from 'react';
import { connectToDatabase } from "../util/mongodb";

export default function Top({ posts }) {
  //const [count, setCount] = useState(1);
  const wrapperRef = useRef(null);

  const copyToClipboard = (index, areaNumber) => {
    // 
    // HERE I AM USING A DYNAMIC CLASS FOR THE WRAPPER REF 
    // AND DYNAMIC QUERY SELECTOR, THEREBY ONLY NEEDING ONE REF ON THE TOP PARENT
    const onePost = wrapperRef.current.querySelector(`.index_${index}`)
    const oneLang = onePost.querySelectorAll('textarea')[areaNumber];
    oneLang.select();
    document.execCommand('copy');
  };

  var allPosts = posts.map((post, index) => {

    var formattedDate = post.date.replace(/T/, ' \xa0\xa0\xa0').split(".")[0]
    var englishHtml = post.en1 + post.en2 + post.en3 + post.en4 + post.en5;
    var frenchHtml = post.fr1 + post.fr2 + post.fr3 + post.fr4 + post.fr5;
    var germanHtml = post.de1 + post.de2 + post.de3 + post.de4 + post.de5;

    return (
      <div className={post.title} key={post._id}>
        <h2>{formattedDate}</h2>
        <h2>{index}</h2>

        <div className={"wrapper index_" + index}>
          <div className="one en">
            <h3>Eng</h3>
            <button onClick={() => {copyToClipboard(index, 0)}}>COPY</button>
            <textarea value={englishHtml} readOnly></textarea>
          </div>

          <div className="one fr">
            <h3>Fr</h3>
            <button onClick={() => {copyToClipboard(index, 1)}}>COPY</button> 
            <textarea value={frenchHtml} readOnly></textarea>
          </div>

          <div className="one de">
            <h3>De</h3>
            <button onClick={() => {copyToClipboard(index, 2)}}>COPY</button>
            <textarea value={germanHtml} readOnly></textarea>
          </div>
        </div>

      </div>
    )
  })

  return (
    <div ref={wrapperRef}>
      <h1>Latest delivery pages </h1>
      <ul>
        {allPosts}
      </ul>

      <style jsx global>{`

        body{
          margin: 0;
          padding: 0;
        }
        h1{
          padding-left: 40px;
          color: grey;
          font-family: system-ui;
          font-variant: all-small-caps;
        }
        .one,.one textarea {
          font-size: 5px;
          height: 200px;
          width: 300px;
          max-width:  350px;
          list-style-type:none;
          padding-inline-start: 0px;
          margin-right: 50px;
          margin-bottom: 150px;
          
        }

        h2{
          font-family: system-ui;
          font-variant: all-small-caps;
        }
        .one h3 {
          font-size: 25px;
          margin-top: 0;
          margin-bottom: 10px;
          font-family: system-ui;
        }

        .one button{
          width: 300px;
          height: 40px;
          margin-bottom: 10px;
        }

        @media screen and (min-width: 768px){
          .wrapper{
            display: flex;
            flex-direction: row;
          }
        }

      `}</style>
    </div>
  );
}
yvt65v4c

yvt65v4c7#

我知道晚了,但我遇到了一个解决方案使用。我认为是天才的孩子节点。我不是100%确定它在你的用例中有效,但对我来说是完美的:

const navList = [
        { title: 'Add an event', link: '/createEvent' },
        { title: 'Contact me', link: '/contact' },
        { title: 'About this site', link: '/about' },
    ];

    // useRef to access DOM elements
    const navBarRef = useRef<HTMLUListElement>(null);
    const navItemRef = useRef<HTMLLIElement>(null);

    // map navList to create navItems
    const navItemsMapped = (
        <ul className={styles.navBar} ref={navBarRef}>
            {navList.map((item, index) => {
                return (
                    // note ref assigned to each li element
                    <li key={index} className={styles.navItem} ref={navItemRef}>
                        <Link href={item.link}>{item.title}</Link>
                    </li>
                );
            })}
        </ul>
    );

    const navButtonClickHandler = () => {
        // navBar styles
        const navBar = navBarRef.current;
        navBar?.classList.toggle(styles.navBarActive);
        // navItem styles
        const navItems = navBar?.childNodes;
        // dynamically assign ref to each navItem based on their status as childNodes of navBar, rather than using a static ref
        navItems?.forEach((item) => {
            const itemRef = item as HTMLLIElement;
            itemRef.classList.toggle(styles.navItemActive);
        });
    };

这里的关键是最后的forEach,其中每个引用都是基于navItem状态作为navBar的子节点来分配的,而不是静态分配的。我还应该注意这是NextJS/TypeScript的摘录。

vojdkbi0

vojdkbi08#

在这种情况下,您需要创建一个空refs数组,并在React组件内部生成refs时推送它们。
然后使用useEffect处理相应的refs。
下面是一个例子:

const refs = []

useEffect(() => {
    refs.map(ref => {
      console.log(ref.current)
      // DO SOMETHING WITH ref.current
    })
  }, [refs])

{postsData.map((post, i) => {
   refs.push(React.createRef())
   return (
     <div ref={refs[i]} key={post.id}>{...}</div>
   )}
}

相关问题