据我所知,我可以像这样为单个元素使用refs:

const { useRef, useState, useEffect } = React; const App = () => { const elRef = useRef(); const [elWidth, setElWidth] = useState(); useEffect(() => { setElWidth(elRef.current.offsetWidth); }, []); return ( <div> <div ref={elRef} style={{ width: "100px" }}> Width is: {elWidth} </div> </div> ); }; ReactDOM.render( <App />, document.getElementById("root") ); <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"></div>

我如何实现这个元素数组?显然不是这样的:(我知道,即使我没有尝试:)

const { useRef, useState, useEffect } = React; const App = () => { const elRef = useRef(); const [elWidth, setElWidth] = useState(); useEffect(() => { setElWidth(elRef.current.offsetWidth); }, []); return ( <div> {[1, 2, 3].map(el => ( <div ref={elRef} style={{ width: `${el * 100}px` }}> Width is: {elWidth} </div> ))} </div> ); }; ReactDOM.render( <App />, document.getElementById("root") ); <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script> <div id="root"></div>

我见过这个,所以才会这样。但是,我仍然不知道如何在这个简单的案例中实现这个建议。


当前回答

更新

新React文档显示了一个使用地图的推荐方法。

点击这里查看测试版(2022年12月)


有两种方法

对多个当前元素使用一个引用

const inputRef = useRef([]);

inputRef.current[idx].focus();

<input
  ref={el => inputRef.current[idx] = el}
/>

const {useRef} = React; const App = () => { const list = [...Array(8).keys()]; const inputRef = useRef([]); const handler = idx => e => { const next = inputRef.current[idx + 1]; if (next) { next.focus() } }; return ( <div className="App"> <div className="input_boxes"> {list.map(x => ( <div> <input key={x} ref={el => inputRef.current[x] = el} onChange={handler(x)} type="number" className="otp_box" /> </div> ))} </div> </div> ); } ReactDOM.render(<App />, document.getElementById("root")); <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

使用ref的数组 正如上面的帖子所说,不建议这样做,因为官方指南(以及内部棉绒检查)不允许它通过。 不要在循环、条件或嵌套函数中调用hook。相反,总是在React函数的顶层使用Hooks。通过遵循此规则,您可以确保在每次组件呈现时以相同的顺序调用hook。 然而,由于这不是我们目前的情况,下面的演示仍然可以工作,只是不推荐。

const inputRef = list.map(x => useRef(null));

inputRef[idx].current.focus();

<input
  ref={inputRef[idx]}
/>

const {useRef} = React; const App = () => { const list = [...Array(8).keys()]; const inputRef = list.map(x => useRef(null)); const handler = idx => () => { const next = inputRef[idx + 1]; if (next) { next.current.focus(); } }; return ( <div className="App"> <div className="input_boxes"> {list.map(x => ( <div> <input key={x} ref={inputRef[x]} onChange={handler(x)} type="number" className="otp_box" /> </div> ))} </div> </div> ); } ReactDOM.render(<App />, document.getElementById("root")); <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

其他回答

当一个元素的ref改变时,React会重新渲染它(参照相等/ "triple-equals"检查)。

这里的大多数答案都没有考虑到这一点。更糟糕的是:当父对象渲染并重新初始化ref对象时,所有子对象都将重新渲染,即使它们是记忆的组件(React。PureComponent或React.memo)!

下面的解决方案没有不必要的重新渲染,使用动态列表,甚至没有引入实际的副作用。无法访问未定义的引用。ref在第一次读取时初始化。之后,它保持参考稳定。

const useGetRef = () => {
  const refs = React.useRef({})
  return React.useCallback(
    (idx) => (refs.current[idx] ??= React.createRef()),
    [refs]
  )
}

const Foo = ({ items }) => {
  const getRef = useGetRef()
  return items.map((item, i) => (
    <div ref={getRef(i)} key={item.id}>
      {/* alternatively, to access refs by id: `getRef(item.id)` */}
      {item.title}
    </div>
  ))
}

警告:当项目随着时间的推移而缩小时,未使用的引用对象将不会被清理。当React卸载一个元素时,它会正确地设置ref[i]。Current = null,但“空”引用将保留。

您可以使用数组(或对象)来跟踪所有的引用,并使用方法向数组添加ref。

注意:如果你添加和删除引用,你必须在每个渲染周期清空数组。

import React, { useRef } from "react";

const MyComponent = () => {
   // intialize as en empty array
   const refs = useRefs([]); // or an {}
   // Make it empty at every render cycle as we will get the full list of it at the end of the render cycle
   refs.current = []; // or an {}

   // since it is an array we need to method to add the refs
   const addToRefs = el => {
     if (el && !refs.current.includes(el)) {
       refs.current.push(el);
     }
    };
    return (
     <div className="App">
       {[1,2,3,4].map(val => (
         <div key={val} ref={addToRefs}>
           {val}
         </div>
       ))}
     </div>
   );

}

工作示例 https://codesandbox.io/s/serene-hermann-kqpsu

我们可以使用数组ref来记住ref列表:

import { RefObject, useRef } from 'react';

type RefObjects<T> = RefObject<T>[];

function convertLengthToRefs<T>(
  length: number,
  initialValue: T | null,
): RefObjects<T> {
  return Array.from(new Array(length)).map<RefObject<T>>(() => ({
    current: initialValue,
  }));
}

export function useRefs<T>(length: number, initialValue: T | null = null) {
  const refs = useRef<RefObjects<T>>(convertLengthToRefs(length, initialValue));

  return refs.current;
}

这是一个演示:

const dataList = [1, 2, 3, 4];

const Component: React.FC = () => {
  const refs = useRefs<HTMLLIElement>(dataList.length, null);

  useEffect(() => {
    refs.forEach((item) => {
      console.log(item.current?.getBoundingClientRect());
    });
  }, []);

  return (
    <ul>
      {dataList.map((item, index) => (
        <li key={item} ref={refs[index]}>
          {item}
        </li>
      ))}
    </ul>
  );
};


我使用useRef钩子创建我想独立控制的数据面板。首先我初始化useRef来存储一个数组:

import React, { useRef } from "react";

const arr = [1, 2, 3];

const refs = useRef([])

在初始化数组时,我们观察到它实际上是这样的:

//refs = {current: []}

然后我们应用map函数来创建面板,使用我们将引用的div标签,将当前元素添加到我们的引用中。带有一个按钮的当前数组:

arr.map((item, index) => {
  <div key={index} ref={(element) => {refs.current[index] = element}}>
    {item}
    <a
      href="#"
      onClick={(e) => {
        e.preventDefault();
        onClick(index)
      }}
    >
      Review
    </a>
})

最后一个函数接收按下按钮的索引,我们可以控制我们想要显示的面板

const onClick = (index) => {
  console.log(index)
  console.log(refs.current[index])
}

最后,完整的代码是这样的

import React, { useRef } from "react";

const arr = [1, 2, 3];

const refs = useRef([])
//refs = {current: []}

const onClick = (index) => {
  console.log(index)
  console.log(refs.current[index])
}

const MyPage = () => {
   const content = arr.map((item, index) => {
     <div key={index} ref={(element) => {refs.current[index] = element}}>
       {item}
       <a
         href="#"
         onClick={(e) => {
           e.preventDefault();
           onClick(index)
         }}
       >
         Review
       </a>
   })
   return content
}

export default MyPage

这对我很管用!希望这些知识对你有用。

由于不能在循环中使用钩子,这里有一个解决方案,以便在数组随时间变化时使其工作。

我想数组来自于道具:

const App = props => {
    const itemsRef = useRef([]);
    // you can access the elements with itemsRef.current[n]

    useEffect(() => {
       itemsRef.current = itemsRef.current.slice(0, props.items.length);
    }, [props.items]);

    return props.items.map((item, i) => (
      <div 
          key={i} 
          ref={el => itemsRef.current[i] = el} 
          style={{ width: `${(i + 1) * 100}px` }}>
        ...
      </div>
    ));
}