【问题标题】:How target DOM with react useRef in map如何在地图中使用 react useRef 定位 DOM
【发布时间】:2019-07-23 05:40:43
【问题描述】:

我正在寻找一个解决方案,通过 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>
}

我怎样才能做到这一点?

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    useRef 只是部分类似于 React 的 ref(只是对象的结构,只有 current 的字段)。

    useRef 钩子旨在在渲染之间存储一些数据,并且更改这些数据不会触发重新渲染(与 useState 不同)。

    也只是温馨提醒:最好避免在循环或if 中初始化挂钩。我是first rule of hooks

    考虑到这一点,我们:

    1. 创建数组并将其保存在useRef的渲染之间

    2. 我们通过createRef()初始化每个数组的元素

    3. 我们可以使用.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钩子中访问它。原因:refs 是在元素渲染后绑定的,所以在渲染期间第一次运行它还没有初始化。

    【讨论】:

    • 您是老板,非常感谢您提供此信息! useRef(elts.map(React.createRef)) 正是我一直在寻找的东西,以防止函数组件丢弃我的 refs。
    • 美丽的???
    • 非常感谢@skyboyer 的解释。我已经使用像这样-const refs = useRef( new Array(2).fill(React.createRef())); 使它成为动态的。这会导致问题吗?
    • 有人知道如何在打字稿版本中实现这个吗?
    • @Shofol 的问题是它会创建一个单一的 ref,然后用它填充 useRef 数组。所以,如果你尝试引用这些项目中的任何一个,它们都会引用一个实体. const refs = useRef([...new Array(3)].map(() =&gt; React.createRef())); 是我找到的更好的选择。
    【解决方案2】:

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

    初始化:

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

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

    用法:

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

    【讨论】:

    • 您能否详细说明为什么首选useMemo 以及您的意思是什么错误?
    • 我得到:“警告:不要在 useEffect(...)、useMemo(...) 或其他内置 Hooks 中调用 Hooks”
    【解决方案3】:

    获取父引用并操作子引用

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

    我以这种方式工作,我认为这比其他建议的答案更简单。

    【讨论】:

    • 有趣的想法
    【解决方案4】:

    您可以将每个地图项分开到组件中,而不是使用引用数组或类似的东西。当你将它们分开时,你可以独立使用useRefs:

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

    【讨论】:

    • 添加了@StephenOstermiller
    • 感谢更新,看起来好多了。
    【解决方案5】:

    如果您提前知道数组的长度,在您的示例中,您可以简单地创建一个 ref 数组,然后通过索引分配每个:

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

    【讨论】:

    • React Hook "useRef" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function 知道为什么吗?
    • @LirenYeo 由于 Hooks Rules,特别是 React 依赖于 Hook 的调用顺序。 在回调中调用的 Hook 会导致不稳定的序列来电。
    • 所以应该是const items = useRef(Array.from({length: 2}, () =&gt; React.createRef()))
    【解决方案6】:

    我遇到了这样的问题并阅读了'Joer's answer并意识到您可以使用索引动态设置 querySelector 类并仅将一个 ref 设置为整个父级。为代码负载道歉,但希望这可以帮助某人:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-12
      • 1970-01-01
      • 2021-05-01
      • 2019-12-06
      • 1970-01-01
      • 1970-01-01
      • 2020-05-09
      相关资源
      最近更新 更多