简体   繁体   English

如何在地图中使用 react useRef 定位 DOM

[英]How target DOM with react useRef in map

I looking for a solution about get an array of DOM elements with react useRef() hook.我正在寻找一个关于使用 react useRef()钩子获取 DOM 元素数组的解决方案。

example:例子:

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

How can I achieve this?我怎样才能做到这一点?

useRef is just partially similar to React's ref (just structure of object with only field of current ). useRef只是部分类似于 React 的ref (只是对象的结构,只有current字段)。

useRef hook is aiming on storing some data between renders and changing that data does not trigger re-rendering(unlike useState does). useRef钩子旨在在渲染之间存储一些数据,并且更改该数据不会触发重新渲染(与useState不同)。

Also just gentle reminder: better avoid initialize hooks in loops or if .也只是温和的提醒:最好避免在循环或if中初始化钩子。 It's first rule of hooks .是钩子的第一条规则

Having this in mind we:考虑到这一点,我们:

  1. create array and keep it between renders by useRef创建数组并通过useRef将其保留在渲染之间

  2. we initialize each array's element by createRef()我们通过createRef()初始化每个数组的元素

  3. we can refer to list by using .current notation我们可以使用.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>) }

This way we can safely modify array(say by changing it's length).这样我们就可以安全地修改数组(比如通过改变它的长度)。 But don't forget that mutating data stored by useRef does not trigger re-render.但是不要忘记useRef存储的变异数据不会触发重新渲染。 So to make changing length to re-render we need to involve useState .因此,要更改长度以重新渲染,我们需要涉及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} />
  </>)
}

Also don't try to access refs.current[0].current at first rendering - it will raise an error.也不要尝试在第一次渲染时访问refs.current[0].current - 它会引发错误。

Say

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

So you either guard it as所以你要么把它当作

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

or access it in useEffect hook.或在useEffect挂钩中访问它。 Reason: ref s are bound after element is rendered so during rendering is running for the first time it is not initialized yet.原因: ref是在元素渲染后绑定的,因此在渲染期间第一次运行它尚未初始化。

I'll expand on skyboyer's answer a bit.我会稍微扩展一下skyboyer的答案 For performance optimization (and to avoid potential weird bugs), you might prefer to use useMemo instead of useRef .对于性能优化(并避免潜在的奇怪错误),您可能更喜欢使用useMemo而不是useRef Because useMemo accepts a callback as an argument instead of a value, React.createRef will only be initialized once, after the first render.因为 useMemo 接受回调作为参数而不是值, React.createRef只会在第一次渲染之后被初始化一次。 Inside the callback you can return an array of createRef values and use the array appropriately.在回调中,您可以返回一个createRef值数组并适当地使用该数组。

Initialization:初始化:

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

Empty array here (as a second argument) tells React to only initialize refs once.这里的空数组(作为第二个参数)告诉 React 只初始化一次 refs。 If ref count changes you may need to pass [x.length] as "a deps array" and create refs dynamically: Array.from({ length: x.length }).map(() => createRef()),如果引用计数发生变化,您可能需要将[x.length]作为“deps 数组”传递并动态创建引用: Array.from({ length: x.length }).map(() => createRef()),

Usage:用法:

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

take the parent Reference and manipulate the childrens获取父参考并操纵孩子

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

I work this way and I think that's more simple than other proposed answers.我以这种方式工作,我认为这比其他建议的答案更简单。

Instead of using array of refs or something like that, you can seperate each map item to component.您可以将每个地图项分开到组件,而不是使用 refs 数组或类似的东西。 When you seperate them, you can use useRef s independently:当你将它们分开时,你可以独立使用useRef

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

If you know the length of the array ahead of time, to which you do in your example you can simply create an array of refs and then assign each one by their index:如果您提前知道数组的长度,在您的示例中,您可以简单地创建一个 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>
  )
}

I had a problem like this and read 'Joer's answer and realised you can just loop through using the index setting the querySelector class dynamically and set only one ref to the overall parent.我遇到了这样的问题并阅读了'Joer's answer并意识到您可以通过使用索引动态设置 querySelector 类并仅将一个引用设置为整个父级。 Apologies for the load of code but hope this helps someone:为代码负载道歉,但希望这可以帮助某人:

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

In this case you would need to create an array of empty refs and push the refs while they are generated inside the React component.在这种情况下,您需要创建一个空 ref 数组并在它们在 React 组件中生成时推送它们。

You would then use useEffect to handle the corresponding refs.然后,您将使用 useEffect 来处理相应的参考。

Here's an example:这是一个例子:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM